Add Paketo image building system tests
A new system test plugin is being made available for running test suites that should be run less frequently than with every commit, such as tests that verify Spring Boot compatibility with external projects. CI pipeline configuration for running system tests is also provided. The first system tests verify the behavior of the Spring Boot image building plugins when building images using Paketo buildpacks. Closes gh-25824pull/27297/head
parent
d82b46b718
commit
18c7c0af22
@ -0,0 +1,83 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2012-2021 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.springframework.boot.build.test;
|
||||||
|
|
||||||
|
import org.gradle.api.Plugin;
|
||||||
|
import org.gradle.api.Project;
|
||||||
|
import org.gradle.api.plugins.JavaPlugin;
|
||||||
|
import org.gradle.api.plugins.JavaPluginConvention;
|
||||||
|
import org.gradle.api.tasks.SourceSet;
|
||||||
|
import org.gradle.api.tasks.SourceSetContainer;
|
||||||
|
import org.gradle.api.tasks.testing.Test;
|
||||||
|
import org.gradle.language.base.plugins.LifecycleBasePlugin;
|
||||||
|
import org.gradle.plugins.ide.eclipse.EclipsePlugin;
|
||||||
|
import org.gradle.plugins.ide.eclipse.model.EclipseModel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A {@link Plugin} to configure system testing support in a {@link Project}.
|
||||||
|
*
|
||||||
|
* @author Andy Wilkinson
|
||||||
|
* @author Scott Frederick
|
||||||
|
*/
|
||||||
|
public class SystemTestPlugin implements Plugin<Project> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Name of the {@code systemTest} task.
|
||||||
|
*/
|
||||||
|
public static String SYSTEM_TEST_TASK_NAME = "systemTest";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Name of the {@code systemTest} source set.
|
||||||
|
*/
|
||||||
|
public static String SYSTEM_TEST_SOURCE_SET_NAME = "systemTest";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void apply(Project project) {
|
||||||
|
project.getPlugins().withType(JavaPlugin.class, (javaPlugin) -> configureSystemTesting(project));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void configureSystemTesting(Project project) {
|
||||||
|
SourceSet systemTestSourceSet = createSourceSet(project);
|
||||||
|
createTestTask(project, systemTestSourceSet);
|
||||||
|
project.getPlugins().withType(EclipsePlugin.class, (eclipsePlugin) -> {
|
||||||
|
EclipseModel eclipse = project.getExtensions().getByType(EclipseModel.class);
|
||||||
|
eclipse.classpath((classpath) -> classpath.getPlusConfigurations().add(
|
||||||
|
project.getConfigurations().getByName(systemTestSourceSet.getRuntimeClasspathConfigurationName())));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private SourceSet createSourceSet(Project project) {
|
||||||
|
SourceSetContainer sourceSets = project.getConvention().getPlugin(JavaPluginConvention.class).getSourceSets();
|
||||||
|
SourceSet systemTestSourceSet = sourceSets.create(SYSTEM_TEST_SOURCE_SET_NAME);
|
||||||
|
SourceSet mainSourceSet = sourceSets.getByName(SourceSet.MAIN_SOURCE_SET_NAME);
|
||||||
|
systemTestSourceSet
|
||||||
|
.setCompileClasspath(systemTestSourceSet.getCompileClasspath().plus(mainSourceSet.getOutput()));
|
||||||
|
systemTestSourceSet
|
||||||
|
.setRuntimeClasspath(systemTestSourceSet.getRuntimeClasspath().plus(mainSourceSet.getOutput()));
|
||||||
|
return systemTestSourceSet;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createTestTask(Project project, SourceSet systemTestSourceSet) {
|
||||||
|
Test systemTest = project.getTasks().create(SYSTEM_TEST_TASK_NAME, Test.class);
|
||||||
|
systemTest.setGroup(LifecycleBasePlugin.VERIFICATION_GROUP);
|
||||||
|
systemTest.setDescription("Runs system tests.");
|
||||||
|
systemTest.setTestClassesDirs(systemTestSourceSet.getOutput().getClassesDirs());
|
||||||
|
systemTest.setClasspath(systemTestSourceSet.getRuntimeClasspath());
|
||||||
|
systemTest.shouldRunAfter(JavaPlugin.TEST_TASK_NAME);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,12 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
source $(dirname $0)/common.sh
|
||||||
|
|
||||||
|
pushd git-repo > /dev/null
|
||||||
|
if [[ -d /opt/openjdk-toolchain ]]; then
|
||||||
|
./gradlew -Dorg.gradle.internal.launcher.welcomeMessageEnabled=false --no-daemon --max-workers=4 --rerun-tasks systemTest -PtoolchainVersion=${TOOLCHAIN_JAVA_VERSION} -Porg.gradle.java.installations.auto-detect=false -Porg.gradle.java.installations.auto-download=false -Porg.gradle.java.installations.paths=/opt/openjdk-toolchain/
|
||||||
|
else
|
||||||
|
./gradlew -Dorg.gradle.internal.launcher.welcomeMessageEnabled=false --no-daemon --max-workers=4 --rerun-tasks systemTest
|
||||||
|
fi
|
||||||
|
popd > /dev/null
|
@ -0,0 +1,21 @@
|
|||||||
|
---
|
||||||
|
platform: linux
|
||||||
|
inputs:
|
||||||
|
- name: git-repo
|
||||||
|
caches:
|
||||||
|
- path: gradle
|
||||||
|
params:
|
||||||
|
BRANCH:
|
||||||
|
CI: true
|
||||||
|
GRADLE_ENTERPRISE_ACCESS_KEY:
|
||||||
|
GRADLE_ENTERPRISE_CACHE_USERNAME:
|
||||||
|
GRADLE_ENTERPRISE_CACHE_PASSWORD:
|
||||||
|
GRADLE_ENTERPRISE_URL: https://ge.spring.io
|
||||||
|
run:
|
||||||
|
path: bash
|
||||||
|
args:
|
||||||
|
- -ec
|
||||||
|
- |
|
||||||
|
source /docker-lib.sh
|
||||||
|
start_docker
|
||||||
|
${PWD}/git-repo/ci/scripts/run-system-tests.sh
|
14
spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/testkit/GradleBuildExtension.java → spring-boot-project/spring-boot-tools/spring-boot-test-support/src/main/java/org/springframework/boot/testsupport/gradle/testkit/GradleBuildExtension.java
14
spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/testkit/GradleBuildExtension.java → spring-boot-project/spring-boot-tools/spring-boot-test-support/src/main/java/org/springframework/boot/testsupport/gradle/testkit/GradleBuildExtension.java
@ -0,0 +1,53 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2012-2021 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.springframework.boot.testsupport.gradle.testkit;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.gradle.api.JavaVersion;
|
||||||
|
import org.gradle.util.GradleVersion;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Versions of Gradle used for testing.
|
||||||
|
*
|
||||||
|
* @author Scott Frederick
|
||||||
|
*/
|
||||||
|
public final class GradleVersions {
|
||||||
|
|
||||||
|
private GradleVersions() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<String> allCompatible() {
|
||||||
|
if (isJava16()) {
|
||||||
|
return Arrays.asList("7.0.2", "7.1");
|
||||||
|
}
|
||||||
|
return Arrays.asList("6.8.3", GradleVersion.current().getVersion(), "7.0.2", "7.1.1");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String currentOrMinimumCompatible() {
|
||||||
|
if (isJava16()) {
|
||||||
|
return "7.0.2";
|
||||||
|
}
|
||||||
|
return GradleVersion.current().getVersion();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isJava16() {
|
||||||
|
return JavaVersion.current().isCompatibleWith(JavaVersion.VERSION_16);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,35 @@
|
|||||||
|
plugins {
|
||||||
|
id 'java-gradle-plugin'
|
||||||
|
id "org.springframework.boot.conventions"
|
||||||
|
id "org.springframework.boot.system-test"
|
||||||
|
}
|
||||||
|
|
||||||
|
description = "Spring Boot Image Building Tests"
|
||||||
|
|
||||||
|
configurations {
|
||||||
|
providedRuntime {
|
||||||
|
extendsFrom dependencyManagement
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
systemTest {
|
||||||
|
if (project.hasProperty("springBootVersion")) {
|
||||||
|
systemProperty "springBootVersion", project.properties["springBootVersion"]
|
||||||
|
} else {
|
||||||
|
systemProperty "springBootVersion", project.getVersion()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-web")) {
|
||||||
|
exclude group: "org.hibernate.validator"
|
||||||
|
}
|
||||||
|
|
||||||
|
systemTestImplementation(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-test"))
|
||||||
|
systemTestImplementation(project(":spring-boot-project:spring-boot-tools:spring-boot-test-support"))
|
||||||
|
systemTestImplementation(project(":spring-boot-project:spring-boot-tools:spring-boot-buildpack-platform"))
|
||||||
|
systemTestImplementation(gradleTestKit())
|
||||||
|
systemTestImplementation("org.assertj:assertj-core")
|
||||||
|
systemTestImplementation("org.testcontainers:junit-jupiter")
|
||||||
|
systemTestImplementation("org.testcontainers:testcontainers")
|
||||||
|
}
|
@ -0,0 +1,134 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2012-2021 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.springframework.boot.image.assertions;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import com.github.dockerjava.api.model.ContainerConfig;
|
||||||
|
import org.assertj.core.api.AbstractAssert;
|
||||||
|
import org.assertj.core.api.AbstractListAssert;
|
||||||
|
import org.assertj.core.api.AbstractObjectAssert;
|
||||||
|
import org.assertj.core.api.AbstractStringAssert;
|
||||||
|
import org.assertj.core.api.Assertions;
|
||||||
|
import org.assertj.core.api.AssertionsForClassTypes;
|
||||||
|
import org.assertj.core.api.InstanceOfAssertFactories;
|
||||||
|
import org.assertj.core.api.ListAssert;
|
||||||
|
import org.assertj.core.api.ObjectAssert;
|
||||||
|
|
||||||
|
import org.springframework.boot.test.json.JsonContentAssert;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AssertJ {@link org.assertj.core.api.Assert} for Docker image container configuration.
|
||||||
|
*
|
||||||
|
* @author Scott Frederick
|
||||||
|
*/
|
||||||
|
public class ContainerConfigAssert extends AbstractAssert<ContainerConfigAssert, ContainerConfig> {
|
||||||
|
|
||||||
|
private static final String BUILD_METADATA_LABEL = "io.buildpacks.build.metadata";
|
||||||
|
|
||||||
|
private static final String LIFECYCLE_METADATA_LABEL = "io.buildpacks.lifecycle.metadata";
|
||||||
|
|
||||||
|
ContainerConfigAssert(ContainerConfig containerConfig) {
|
||||||
|
super(containerConfig, ContainerConfigAssert.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public BuildMetadataAssert buildMetadata() {
|
||||||
|
return new BuildMetadataAssert(jsonLabel(BUILD_METADATA_LABEL));
|
||||||
|
}
|
||||||
|
|
||||||
|
public LifecycleMetadataAssert lifecycleMetadata() {
|
||||||
|
return new LifecycleMetadataAssert(jsonLabel(LIFECYCLE_METADATA_LABEL));
|
||||||
|
}
|
||||||
|
|
||||||
|
public AbstractStringAssert<?> label(String label) {
|
||||||
|
return AssertionsForClassTypes.assertThat(getLabel(label));
|
||||||
|
}
|
||||||
|
|
||||||
|
private JsonContentAssert jsonLabel(String label) {
|
||||||
|
return new JsonContentAssert(ContainerConfigAssert.class, getLabel(label));
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getLabel(String label) {
|
||||||
|
Map<String, String> labels = this.actual.getLabels();
|
||||||
|
if (labels == null) {
|
||||||
|
failWithMessage("Container config contains no labels");
|
||||||
|
}
|
||||||
|
if (!labels.containsKey(label)) {
|
||||||
|
failWithActualExpectedAndMessage(labels, label, "Expected label not found in container config");
|
||||||
|
}
|
||||||
|
return labels.get(label);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Asserts for the JSON content in the {@code io.buildpacks.build.metadata} label.
|
||||||
|
*
|
||||||
|
* See <a href=
|
||||||
|
* "https://github.com/buildpacks/spec/blob/main/platform.md#iobuildpacksbuildmetadata-json">the
|
||||||
|
* spec</a>
|
||||||
|
*/
|
||||||
|
public static class BuildMetadataAssert extends AbstractAssert<BuildMetadataAssert, JsonContentAssert> {
|
||||||
|
|
||||||
|
BuildMetadataAssert(JsonContentAssert jsonContentAssert) {
|
||||||
|
super(jsonContentAssert, BuildMetadataAssert.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ListAssert<Object> buildpacks() {
|
||||||
|
return this.actual.extractingJsonPathArrayValue("$.buildpacks[*].id");
|
||||||
|
}
|
||||||
|
|
||||||
|
public ListAssert<Object> bomDependencies() {
|
||||||
|
return this.actual
|
||||||
|
.extractingJsonPathArrayValue("$.bom[?(@.name=='dependencies')].metadata.dependencies[*].name");
|
||||||
|
}
|
||||||
|
|
||||||
|
public AbstractStringAssert<?> bomJavaVersion(String javaType) {
|
||||||
|
return this.actual.extractingJsonPathArrayValue("$.bom[?(@.name=='%s')].metadata.version", javaType)
|
||||||
|
.singleElement(Assertions.as(InstanceOfAssertFactories.STRING));
|
||||||
|
}
|
||||||
|
|
||||||
|
public AbstractObjectAssert<?, Object> processOfType(String type) {
|
||||||
|
return this.actual.extractingJsonPathArrayValue("$.processes[?(@.type=='%s')]", type).singleElement();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Asserts for the the JSON content in the {@code io.buildpacks.lifecycle.metadata}
|
||||||
|
* label.
|
||||||
|
*
|
||||||
|
* See <a href=
|
||||||
|
* "https://github.com/buildpacks/spec/blob/main/platform.md#iobuildpackslifecyclemetadata-json">the
|
||||||
|
* spec</a>
|
||||||
|
*/
|
||||||
|
public static class LifecycleMetadataAssert extends AbstractAssert<LifecycleMetadataAssert, JsonContentAssert> {
|
||||||
|
|
||||||
|
LifecycleMetadataAssert(JsonContentAssert jsonContentAssert) {
|
||||||
|
super(jsonContentAssert, LifecycleMetadataAssert.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ListAssert<Object> buildpackLayers(String buildpackId) {
|
||||||
|
return this.actual.extractingJsonPathArrayValue("$.buildpacks[?(@.key=='%s')].layers", buildpackId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public AbstractListAssert<?, List<?>, Object, ObjectAssert<Object>> appLayerShas() {
|
||||||
|
return this.actual.extractingJsonPathArrayValue("$.app").extracting("sha");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,91 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2012-2021 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.springframework.boot.image.assertions;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
|
||||||
|
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
|
||||||
|
import org.assertj.core.api.AbstractAssert;
|
||||||
|
import org.assertj.core.api.Assertions;
|
||||||
|
import org.assertj.core.api.ListAssert;
|
||||||
|
|
||||||
|
import org.springframework.boot.buildpack.platform.docker.DockerApi;
|
||||||
|
import org.springframework.boot.buildpack.platform.docker.type.ImageReference;
|
||||||
|
import org.springframework.boot.buildpack.platform.docker.type.Layer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AssertJ {@link org.assertj.core.api.Assert} for Docker image contents.
|
||||||
|
*
|
||||||
|
* @author Scott Frederick
|
||||||
|
*/
|
||||||
|
public class ImageAssert extends AbstractAssert<ImageAssert, ImageReference> {
|
||||||
|
|
||||||
|
private final HashMap<String, Layer> layers = new HashMap<>();
|
||||||
|
|
||||||
|
ImageAssert(ImageReference imageReference) throws IOException {
|
||||||
|
super(imageReference, ImageAssert.class);
|
||||||
|
getLayers();
|
||||||
|
}
|
||||||
|
|
||||||
|
public LayerContentAssert hasLayer(String layerDigest) {
|
||||||
|
if (!this.layers.containsKey(layerDigest)) {
|
||||||
|
failWithMessage("Layer with digest '%s' not found in image", layerDigest);
|
||||||
|
}
|
||||||
|
return new LayerContentAssert(this.layers.get(layerDigest));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void getLayers() throws IOException {
|
||||||
|
new DockerApi().image().exportLayers(this.actual, (id, tarArchive) -> {
|
||||||
|
Layer layer = Layer.fromTarArchive(tarArchive);
|
||||||
|
this.layers.put(layer.getId().toString(), layer);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Asserts for image layers.
|
||||||
|
*/
|
||||||
|
public static class LayerContentAssert extends AbstractAssert<LayerContentAssert, Layer> {
|
||||||
|
|
||||||
|
public LayerContentAssert(Layer layer) {
|
||||||
|
super(layer, LayerContentAssert.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ListAssert<String> entries() throws IOException {
|
||||||
|
List<String> entryNames = new ArrayList<>();
|
||||||
|
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||||
|
this.actual.writeTo(out);
|
||||||
|
try (TarArchiveInputStream in = new TarArchiveInputStream(new ByteArrayInputStream(out.toByteArray()))) {
|
||||||
|
TarArchiveEntry entry = in.getNextTarEntry();
|
||||||
|
while (entry != null) {
|
||||||
|
if (!entry.isDirectory()) {
|
||||||
|
entryNames.add(entry.getName().replaceFirst("^/workspace/", ""));
|
||||||
|
}
|
||||||
|
entry = in.getNextTarEntry();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Assertions.assertThat(entryNames);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,44 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2012-2021 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.springframework.boot.image.assertions;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import com.github.dockerjava.api.model.ContainerConfig;
|
||||||
|
|
||||||
|
import org.springframework.boot.buildpack.platform.docker.type.ImageReference;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Factory class for custom AssertJ {@link org.assertj.core.api.Assert}s related to images
|
||||||
|
* and containers.
|
||||||
|
*
|
||||||
|
* @author Scott Frederick
|
||||||
|
*/
|
||||||
|
public final class ImageAssertions {
|
||||||
|
|
||||||
|
private ImageAssertions() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ContainerConfigAssert assertThat(ContainerConfig containerConfig) {
|
||||||
|
return new ContainerConfigAssert(containerConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ImageAssert assertThat(ImageReference imageReference) throws IOException {
|
||||||
|
return new ImageAssert(imageReference);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,54 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2012-2021 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.springframework.boot.image.junit;
|
||||||
|
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.extension.BeforeEachCallback;
|
||||||
|
import org.junit.jupiter.api.extension.ExtensionContext;
|
||||||
|
|
||||||
|
import org.springframework.boot.testsupport.gradle.testkit.GradleBuild;
|
||||||
|
import org.springframework.boot.testsupport.gradle.testkit.GradleVersions;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
import org.springframework.util.ReflectionUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A {@link BeforeEachCallback} to configure and set a test class's {@code gradleBuild}
|
||||||
|
* field prior to test execution.
|
||||||
|
*
|
||||||
|
* @author Scott Frederick
|
||||||
|
*/
|
||||||
|
public class GradleBuildInjectionExtension implements BeforeEachCallback {
|
||||||
|
|
||||||
|
private final GradleBuild gradleBuild;
|
||||||
|
|
||||||
|
GradleBuildInjectionExtension() {
|
||||||
|
this.gradleBuild = new GradleBuild();
|
||||||
|
this.gradleBuild.gradleVersion(GradleVersions.currentOrMinimumCompatible());
|
||||||
|
String bootVersion = System.getProperty("springBootVersion");
|
||||||
|
Assert.notNull(bootVersion, "Property 'springBootVersion' must be set in build environment");
|
||||||
|
this.gradleBuild.bootVersion(bootVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void beforeEach(ExtensionContext context) throws Exception {
|
||||||
|
Field field = ReflectionUtils.findField(context.getRequiredTestClass(), "gradleBuild");
|
||||||
|
field.setAccessible(true);
|
||||||
|
field.set(context.getRequiredTestInstance(), this.gradleBuild);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,53 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2012-2021 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.springframework.boot.image.paketo;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.jar.JarFile;
|
||||||
|
import java.util.zip.ZipEntry;
|
||||||
|
|
||||||
|
import org.yaml.snakeyaml.Yaml;
|
||||||
|
import org.yaml.snakeyaml.constructor.Constructor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Index file describing the layers in the jar or war file and the files or directories in
|
||||||
|
* each layer.
|
||||||
|
*
|
||||||
|
* @author Scott Frederick
|
||||||
|
*/
|
||||||
|
class LayersIndex extends ArrayList<Map<String, List<String>>> {
|
||||||
|
|
||||||
|
List<String> getLayer(String layerName) {
|
||||||
|
return stream().filter((entry) -> entry.containsKey(layerName)).findFirst().map((entry) -> entry.get(layerName))
|
||||||
|
.orElse(Collections.emptyList());
|
||||||
|
}
|
||||||
|
|
||||||
|
static LayersIndex fromArchiveFile(File archiveFile) throws IOException {
|
||||||
|
String indexPath = (archiveFile.getName().endsWith(".war") ? "WEB-INF/layers.idx" : "BOOT-INF/layers.idx");
|
||||||
|
try (JarFile jarFile = new JarFile(archiveFile)) {
|
||||||
|
ZipEntry indexEntry = jarFile.getEntry(indexPath);
|
||||||
|
Yaml yaml = new Yaml(new Constructor(LayersIndex.class));
|
||||||
|
return yaml.load(jarFile.getInputStream(indexEntry));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,402 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2012-2021 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.springframework.boot.image.paketo;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileWriter;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.PrintWriter;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
import java.util.jar.Attributes;
|
||||||
|
import java.util.jar.JarFile;
|
||||||
|
|
||||||
|
import com.github.dockerjava.api.model.ContainerConfig;
|
||||||
|
import org.assertj.core.api.Condition;
|
||||||
|
import org.gradle.testkit.runner.BuildResult;
|
||||||
|
import org.gradle.testkit.runner.TaskOutcome;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.testcontainers.containers.GenericContainer;
|
||||||
|
import org.testcontainers.containers.wait.strategy.Wait;
|
||||||
|
|
||||||
|
import org.springframework.boot.buildpack.platform.docker.DockerApi;
|
||||||
|
import org.springframework.boot.buildpack.platform.docker.type.ImageName;
|
||||||
|
import org.springframework.boot.buildpack.platform.docker.type.ImageReference;
|
||||||
|
import org.springframework.boot.testsupport.gradle.testkit.GradleBuild;
|
||||||
|
import org.springframework.boot.testsupport.gradle.testkit.GradleBuildExtension;
|
||||||
|
import org.springframework.boot.image.assertions.ImageAssertions;
|
||||||
|
import org.springframework.boot.image.junit.GradleBuildInjectionExtension;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Integration tests for the Paketo builder and buildpacks.
|
||||||
|
*
|
||||||
|
* See
|
||||||
|
* https://paketo.io/docs/buildpacks/language-family-buildpacks/java/#additional-metadata
|
||||||
|
*
|
||||||
|
* @author Scott Frederick
|
||||||
|
*/
|
||||||
|
@ExtendWith({ GradleBuildInjectionExtension.class, GradleBuildExtension.class })
|
||||||
|
class PaketoBuilderTests {
|
||||||
|
|
||||||
|
GradleBuild gradleBuild;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void executableJarApp() throws Exception {
|
||||||
|
writeMainClass();
|
||||||
|
String imageName = "paketo-integration/" + this.gradleBuild.getProjectDir().getName();
|
||||||
|
ImageReference imageReference = ImageReference.of(ImageName.of(imageName));
|
||||||
|
BuildResult result = buildImage(imageName);
|
||||||
|
assertThat(result.task(":bootBuildImage").getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
|
||||||
|
try (GenericContainer<?> container = new GenericContainer<>(imageName).withExposedPorts(8080)) {
|
||||||
|
container.waitingFor(Wait.forHttp("/test")).start();
|
||||||
|
ContainerConfig config = container.getContainerInfo().getConfig();
|
||||||
|
assertLabelsMatchManifestAttributes(config);
|
||||||
|
ImageAssertions.assertThat(config).buildMetadata().buildpacks().containsExactly(
|
||||||
|
"paketo-buildpacks/ca-certificates", "paketo-buildpacks/bellsoft-liberica",
|
||||||
|
"paketo-buildpacks/executable-jar", "paketo-buildpacks/dist-zip", "paketo-buildpacks/spring-boot");
|
||||||
|
ImageAssertions.assertThat(config).buildMetadata().bomDependencies().contains("spring-beans", "spring-boot",
|
||||||
|
"spring-boot-autoconfigure", "spring-boot-jarmode-layertools", "spring-context", "spring-core",
|
||||||
|
"spring-web");
|
||||||
|
ImageAssertions.assertThat(config).buildMetadata().bomJavaVersion("jre").startsWith(javaMajorVersion());
|
||||||
|
ImageAssertions.assertThat(config).buildMetadata().processOfType("web").extracting("command", "args")
|
||||||
|
.containsExactly("java", Collections.singletonList("org.springframework.boot.loader.JarLauncher"));
|
||||||
|
ImageAssertions.assertThat(config).buildMetadata().processOfType("executable-jar")
|
||||||
|
.extracting("command", "args")
|
||||||
|
.containsExactly("java", Collections.singletonList("org.springframework.boot.loader.JarLauncher"));
|
||||||
|
assertImageLayersMatchLayersIndex(imageReference, config);
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
removeImage(imageReference);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void executableJarAppWithAdditionalArgs() throws Exception {
|
||||||
|
writeMainClass();
|
||||||
|
String imageName = "paketo-integration/" + this.gradleBuild.getProjectDir().getName();
|
||||||
|
ImageReference imageReference = ImageReference.of(ImageName.of(imageName));
|
||||||
|
BuildResult result = buildImage(imageName);
|
||||||
|
assertThat(result.task(":bootBuildImage").getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
|
||||||
|
try (GenericContainer<?> container = new GenericContainer<>(imageName).withCommand("--server.port=9090")
|
||||||
|
.withExposedPorts(9090)) {
|
||||||
|
container.waitingFor(Wait.forHttp("/test")).start();
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
removeImage(imageReference);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void executableJarAppBuiltTwiceWithCaching() throws Exception {
|
||||||
|
writeMainClass();
|
||||||
|
String imageName = "paketo-integration/" + this.gradleBuild.getProjectDir().getName();
|
||||||
|
ImageReference imageReference = ImageReference.of(ImageName.of(imageName));
|
||||||
|
BuildResult result = buildImage(imageName);
|
||||||
|
assertThat(result.task(":bootBuildImage").getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
|
||||||
|
try (GenericContainer<?> container = new GenericContainer<>(imageName).withExposedPorts(8080)) {
|
||||||
|
container.waitingFor(Wait.forHttp("/test")).start();
|
||||||
|
container.stop();
|
||||||
|
}
|
||||||
|
result = buildImage(imageName);
|
||||||
|
assertThat(result.task(":bootBuildImage").getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
|
||||||
|
try (GenericContainer<?> container = new GenericContainer<>(imageName).withExposedPorts(8080)) {
|
||||||
|
container.waitingFor(Wait.forHttp("/test")).start();
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
removeImage(imageReference);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void bootDistZipJarApp() throws Exception {
|
||||||
|
writeMainClass();
|
||||||
|
String projectName = this.gradleBuild.getProjectDir().getName();
|
||||||
|
String imageName = "paketo-integration/" + projectName;
|
||||||
|
ImageReference imageReference = ImageReference.of(ImageName.of(imageName));
|
||||||
|
BuildResult result = buildImage(imageName, "assemble", "bootDistZip");
|
||||||
|
assertThat(result.task(":bootBuildImage").getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
|
||||||
|
try (GenericContainer<?> container = new GenericContainer<>(imageName).withExposedPorts(8080)) {
|
||||||
|
container.waitingFor(Wait.forHttp("/test")).start();
|
||||||
|
ContainerConfig config = container.getContainerInfo().getConfig();
|
||||||
|
ImageAssertions.assertThat(config).buildMetadata().buildpacks().containsExactly(
|
||||||
|
"paketo-buildpacks/ca-certificates", "paketo-buildpacks/bellsoft-liberica",
|
||||||
|
"paketo-buildpacks/dist-zip", "paketo-buildpacks/spring-boot");
|
||||||
|
ImageAssertions.assertThat(config).buildMetadata().bomJavaVersion("jre").startsWith(javaMajorVersion());
|
||||||
|
ImageAssertions.assertThat(config).buildMetadata().processOfType("web").extracting("command", "args")
|
||||||
|
.containsExactly("/workspace/" + projectName + "-boot/bin/" + projectName, Collections.emptyList());
|
||||||
|
ImageAssertions.assertThat(config).buildMetadata().processOfType("dist-zip").extracting("command", "args")
|
||||||
|
.containsExactly("/workspace/" + projectName + "-boot/bin/" + projectName, Collections.emptyList());
|
||||||
|
DigestCapturingCondition digests = new DigestCapturingCondition();
|
||||||
|
ImageAssertions.assertThat(config).lifecycleMetadata().appLayerShas().haveExactly(1, digests);
|
||||||
|
ImageAssertions.assertThat(imageReference).hasLayer(digests.getDigest(0)).entries().contains(
|
||||||
|
projectName + "-boot/bin/" + projectName, projectName + "-boot/lib/" + projectName + ".jar");
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
removeImage(imageReference);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void plainDistZipJarApp() throws Exception {
|
||||||
|
writeMainClass();
|
||||||
|
String projectName = this.gradleBuild.getProjectDir().getName();
|
||||||
|
String imageName = "paketo-integration/" + projectName;
|
||||||
|
ImageReference imageReference = ImageReference.of(ImageName.of(imageName));
|
||||||
|
BuildResult result = buildImage(imageName, "assemble", "bootDistZip");
|
||||||
|
assertThat(result.task(":bootBuildImage").getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
|
||||||
|
try (GenericContainer<?> container = new GenericContainer<>(imageName).withExposedPorts(8080)) {
|
||||||
|
container.waitingFor(Wait.forHttp("/test")).start();
|
||||||
|
ContainerConfig config = container.getContainerInfo().getConfig();
|
||||||
|
ImageAssertions.assertThat(config).buildMetadata().buildpacks().containsExactly(
|
||||||
|
"paketo-buildpacks/ca-certificates", "paketo-buildpacks/bellsoft-liberica",
|
||||||
|
"paketo-buildpacks/dist-zip", "paketo-buildpacks/spring-boot");
|
||||||
|
ImageAssertions.assertThat(config).buildMetadata().bomJavaVersion("jre").startsWith(javaMajorVersion());
|
||||||
|
ImageAssertions.assertThat(config).buildMetadata().processOfType("web").extracting("command", "args")
|
||||||
|
.containsExactly("/workspace/" + projectName + "/bin/" + projectName, Collections.emptyList());
|
||||||
|
ImageAssertions.assertThat(config).buildMetadata().processOfType("dist-zip").extracting("command", "args")
|
||||||
|
.containsExactly("/workspace/" + projectName + "/bin/" + projectName, Collections.emptyList());
|
||||||
|
DigestCapturingCondition digests = new DigestCapturingCondition();
|
||||||
|
ImageAssertions.assertThat(config).lifecycleMetadata().appLayerShas().haveExactly(1, digests);
|
||||||
|
ImageAssertions.assertThat(imageReference).hasLayer(digests.getDigest(0)).entries()
|
||||||
|
.contains(projectName + "/bin/" + projectName, projectName + "/lib/" + projectName + "-plain.jar")
|
||||||
|
.anyMatch((s) -> s.startsWith(projectName + "/lib/spring-boot-"))
|
||||||
|
.anyMatch((s) -> s.startsWith(projectName + "/lib/spring-core-"))
|
||||||
|
.anyMatch((s) -> s.startsWith(projectName + "/lib/spring-web-"));
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
removeImage(imageReference);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void executableWarApp() throws Exception {
|
||||||
|
writeMainClass();
|
||||||
|
writeServletInitializerClass();
|
||||||
|
String imageName = "paketo-integration/" + this.gradleBuild.getProjectDir().getName();
|
||||||
|
ImageReference imageReference = ImageReference.of(ImageName.of(imageName));
|
||||||
|
BuildResult result = buildImage(imageName);
|
||||||
|
assertThat(result.task(":bootBuildImage").getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
|
||||||
|
try (GenericContainer<?> container = new GenericContainer<>(imageName).withExposedPorts(8080)) {
|
||||||
|
container.waitingFor(Wait.forHttp("/test")).start();
|
||||||
|
ContainerConfig config = container.getContainerInfo().getConfig();
|
||||||
|
assertLabelsMatchManifestAttributes(config);
|
||||||
|
ImageAssertions.assertThat(config).buildMetadata().buildpacks().containsExactly(
|
||||||
|
"paketo-buildpacks/ca-certificates", "paketo-buildpacks/bellsoft-liberica",
|
||||||
|
"paketo-buildpacks/executable-jar", "paketo-buildpacks/dist-zip", "paketo-buildpacks/spring-boot");
|
||||||
|
ImageAssertions.assertThat(config).buildMetadata().bomDependencies().contains("spring-beans", "spring-boot",
|
||||||
|
"spring-boot-autoconfigure", "spring-boot-jarmode-layertools", "spring-context", "spring-core",
|
||||||
|
"spring-web");
|
||||||
|
ImageAssertions.assertThat(config).buildMetadata().bomJavaVersion("jre").startsWith(javaMajorVersion());
|
||||||
|
ImageAssertions.assertThat(config).buildMetadata().processOfType("web").extracting("command", "args")
|
||||||
|
.containsExactly("java", Collections.singletonList("org.springframework.boot.loader.WarLauncher"));
|
||||||
|
ImageAssertions.assertThat(config).buildMetadata().processOfType("executable-jar")
|
||||||
|
.extracting("command", "args")
|
||||||
|
.containsExactly("java", Collections.singletonList("org.springframework.boot.loader.WarLauncher"));
|
||||||
|
assertImageLayersMatchLayersIndex(imageReference, config);
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
removeImage(imageReference);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void plainWarApp() throws Exception {
|
||||||
|
writeMainClass();
|
||||||
|
writeServletInitializerClass();
|
||||||
|
String imageName = "paketo-integration/" + this.gradleBuild.getProjectDir().getName();
|
||||||
|
ImageReference imageReference = ImageReference.of(ImageName.of(imageName));
|
||||||
|
BuildResult result = buildImage(imageName);
|
||||||
|
assertThat(result.task(":bootBuildImage").getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
|
||||||
|
try (GenericContainer<?> container = new GenericContainer<>(imageName).withExposedPorts(8080)) {
|
||||||
|
container.waitingFor(Wait.forHttp("/test")).start();
|
||||||
|
ContainerConfig config = container.getContainerInfo().getConfig();
|
||||||
|
ImageAssertions.assertThat(config).buildMetadata().buildpacks().containsExactly(
|
||||||
|
"paketo-buildpacks/ca-certificates", "paketo-buildpacks/bellsoft-liberica",
|
||||||
|
"paketo-buildpacks/apache-tomcat", "paketo-buildpacks/dist-zip", "paketo-buildpacks/spring-boot");
|
||||||
|
ImageAssertions.assertThat(config).buildMetadata().bomJavaVersion("jre").startsWith(javaMajorVersion());
|
||||||
|
ImageAssertions.assertThat(config).buildMetadata().processOfType("web").extracting("command", "args")
|
||||||
|
.containsExactly("catalina.sh", Collections.singletonList("run"));
|
||||||
|
ImageAssertions.assertThat(config).buildMetadata().processOfType("tomcat").extracting("command", "args")
|
||||||
|
.containsExactly("catalina.sh", Collections.singletonList("run"));
|
||||||
|
DigestCapturingCondition digests = new DigestCapturingCondition();
|
||||||
|
ImageAssertions.assertThat(config).lifecycleMetadata().appLayerShas().haveExactly(1, digests);
|
||||||
|
ImageAssertions.assertThat(imageReference).hasLayer(digests.getDigest(0)).entries()
|
||||||
|
.contains("WEB-INF/classes/example/ExampleApplication.class",
|
||||||
|
"WEB-INF/classes/example/HelloController.class", "META-INF/MANIFEST.MF")
|
||||||
|
.anyMatch((s) -> s.startsWith("WEB-INF/lib/spring-boot-"))
|
||||||
|
.anyMatch((s) -> s.startsWith("WEB-INF/lib/spring-core-"))
|
||||||
|
.anyMatch((s) -> s.startsWith("WEB-INF/lib/spring-web-"));
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
removeImage(imageReference);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public BuildResult buildImage(String imageName, String... arguments) {
|
||||||
|
String[] buildImageArgs = { "bootBuildImage", "--imageName=" + imageName, "--pullPolicy=IF_NOT_PRESENT" };
|
||||||
|
String[] args = StringUtils.concatenateStringArrays(arguments, buildImageArgs);
|
||||||
|
return this.gradleBuild.build(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeMainClass() throws IOException {
|
||||||
|
writeProjectFile("ExampleApplication.java", (writer) -> {
|
||||||
|
writer.println("package example;");
|
||||||
|
writer.println();
|
||||||
|
writer.println("import org.springframework.boot.SpringApplication;");
|
||||||
|
writer.println("import org.springframework.boot.autoconfigure.SpringBootApplication;");
|
||||||
|
writer.println("import org.springframework.stereotype.Controller;");
|
||||||
|
writer.println("import org.springframework.web.bind.annotation.RequestMapping;");
|
||||||
|
writer.println("import org.springframework.web.bind.annotation.ResponseBody;");
|
||||||
|
writer.println();
|
||||||
|
writer.println("@SpringBootApplication");
|
||||||
|
writer.println("public class ExampleApplication {");
|
||||||
|
writer.println();
|
||||||
|
writer.println(" public static void main(String[] args) {");
|
||||||
|
writer.println(" SpringApplication.run(ExampleApplication.class, args);");
|
||||||
|
writer.println(" }");
|
||||||
|
writer.println();
|
||||||
|
writer.println("}");
|
||||||
|
writer.println();
|
||||||
|
writer.println("@Controller");
|
||||||
|
writer.println("class HelloController {");
|
||||||
|
writer.println();
|
||||||
|
writer.println(" @RequestMapping(\"/test\")");
|
||||||
|
writer.println(" @ResponseBody");
|
||||||
|
writer.println(" String home() {");
|
||||||
|
writer.println(" return \"Hello, world!\";");
|
||||||
|
writer.println(" }");
|
||||||
|
writer.println();
|
||||||
|
writer.println("}");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeServletInitializerClass() throws IOException {
|
||||||
|
writeProjectFile("ServletInitializer.java", (writer) -> {
|
||||||
|
writer.println("package example;");
|
||||||
|
writer.println();
|
||||||
|
writer.println("import org.springframework.boot.builder.SpringApplicationBuilder;");
|
||||||
|
writer.println("import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;");
|
||||||
|
writer.println();
|
||||||
|
writer.println("public class ServletInitializer extends SpringBootServletInitializer {");
|
||||||
|
writer.println();
|
||||||
|
writer.println(" @Override");
|
||||||
|
writer.println(" protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {");
|
||||||
|
writer.println(" return application.sources(ExampleApplication.class);");
|
||||||
|
writer.println(" }");
|
||||||
|
writer.println();
|
||||||
|
writer.println("}");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeProjectFile(String fileName, Consumer<PrintWriter> consumer) throws IOException {
|
||||||
|
File examplePackage = new File(this.gradleBuild.getProjectDir(), "src/main/java/example");
|
||||||
|
examplePackage.mkdirs();
|
||||||
|
File main = new File(examplePackage, fileName);
|
||||||
|
try (PrintWriter writer = new PrintWriter(new FileWriter(main))) {
|
||||||
|
consumer.accept(writer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertLabelsMatchManifestAttributes(ContainerConfig config) throws IOException {
|
||||||
|
JarFile jarFile = new JarFile(projectArchiveFile());
|
||||||
|
Attributes attributes = jarFile.getManifest().getMainAttributes();
|
||||||
|
ImageAssertions.assertThat(config).label("org.springframework.boot.version")
|
||||||
|
.isEqualTo(attributes.getValue("Spring-Boot-Version"));
|
||||||
|
ImageAssertions.assertThat(config).label("org.opencontainers.image.title")
|
||||||
|
.isEqualTo(attributes.getValue(Attributes.Name.IMPLEMENTATION_TITLE));
|
||||||
|
ImageAssertions.assertThat(config).label("org.opencontainers.image.version")
|
||||||
|
.isEqualTo(attributes.getValue(Attributes.Name.IMPLEMENTATION_VERSION));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertImageLayersMatchLayersIndex(ImageReference imageReference, ContainerConfig config)
|
||||||
|
throws IOException {
|
||||||
|
DigestCapturingCondition digests = new DigestCapturingCondition();
|
||||||
|
ImageAssertions.assertThat(config).lifecycleMetadata().appLayerShas().haveExactly(5, digests);
|
||||||
|
LayersIndex layersIndex = LayersIndex.fromArchiveFile(projectArchiveFile());
|
||||||
|
ImageAssertions.assertThat(imageReference).hasLayer(digests.getDigest(0)).entries()
|
||||||
|
.allMatch((entry) -> startsWithOneOf(entry, layersIndex.getLayer("dependencies")));
|
||||||
|
ImageAssertions.assertThat(imageReference).hasLayer(digests.getDigest(1)).entries()
|
||||||
|
.allMatch((entry) -> startsWithOneOf(entry, layersIndex.getLayer("spring-boot-loader")));
|
||||||
|
ImageAssertions.assertThat(imageReference).hasLayer(digests.getDigest(2)).entries()
|
||||||
|
.allMatch((entry) -> startsWithOneOf(entry, layersIndex.getLayer("snapshot-dependencies")));
|
||||||
|
ImageAssertions.assertThat(imageReference).hasLayer(digests.getDigest(3)).entries()
|
||||||
|
.allMatch((entry) -> startsWithOneOf(entry, layersIndex.getLayer("application")));
|
||||||
|
ImageAssertions.assertThat(imageReference).hasLayer(digests.getDigest(4)).entries()
|
||||||
|
.allMatch((entry) -> entry.contains("lib/spring-cloud-bindings-"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private File projectArchiveFile() {
|
||||||
|
return new File(this.gradleBuild.getProjectDir(), "build/libs").listFiles()[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
private String javaMajorVersion() {
|
||||||
|
String javaVersion = System.getProperty("java.version");
|
||||||
|
if (javaVersion.startsWith("1.")) {
|
||||||
|
return javaVersion.substring(2, 3);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
int firstDotIndex = javaVersion.indexOf(".");
|
||||||
|
if (firstDotIndex != -1) {
|
||||||
|
return javaVersion.substring(0, firstDotIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return javaVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean startsWithOneOf(String actual, List<String> expectedPrefixes) {
|
||||||
|
for (String prefix : expectedPrefixes) {
|
||||||
|
if (actual.startsWith(prefix)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void removeImage(ImageReference image) throws IOException {
|
||||||
|
new DockerApi().image().remove(image, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class DigestCapturingCondition extends Condition<Object> {
|
||||||
|
|
||||||
|
private static List<String> digests;
|
||||||
|
|
||||||
|
DigestCapturingCondition() {
|
||||||
|
super(predicate(), "a value starting with 'sha256:'");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Predicate<Object> predicate() {
|
||||||
|
digests = new ArrayList<>();
|
||||||
|
return (sha) -> {
|
||||||
|
digests.add(sha.toString());
|
||||||
|
return sha.toString().startsWith("sha256:");
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
String getDigest(int index) {
|
||||||
|
return digests.get(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,33 @@
|
|||||||
|
plugins {
|
||||||
|
id 'org.springframework.boot' version '{bootVersion}'
|
||||||
|
id 'io.spring.dependency-management' version '{dependencyManagementPluginVersion}'
|
||||||
|
id 'java'
|
||||||
|
id 'application'
|
||||||
|
}
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
maven { url 'https://repo.spring.io/milestone' }
|
||||||
|
maven { url 'https://repo.spring.io/snapshot' }
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation("org.springframework.boot:spring-boot-starter-web:{bootVersion}")
|
||||||
|
}
|
||||||
|
|
||||||
|
bootJar {
|
||||||
|
manifest {
|
||||||
|
attributes(
|
||||||
|
'Implementation-Version': '1.0.0',
|
||||||
|
'Implementation-Title': "Paketo Test"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
application {
|
||||||
|
mainClass = 'example.ExampleApplication'
|
||||||
|
}
|
||||||
|
|
||||||
|
bootBuildImage {
|
||||||
|
archiveFile = new File("${buildDir}/distributions/${project.name}-boot.zip")
|
||||||
|
}
|
@ -0,0 +1,26 @@
|
|||||||
|
plugins {
|
||||||
|
id 'org.springframework.boot' version '{bootVersion}'
|
||||||
|
id 'io.spring.dependency-management' version '{dependencyManagementPluginVersion}'
|
||||||
|
id 'java'
|
||||||
|
id 'war'
|
||||||
|
}
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
maven { url 'https://repo.spring.io/milestone' }
|
||||||
|
maven { url 'https://repo.spring.io/snapshot' }
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation("org.springframework.boot:spring-boot-starter-web:{bootVersion}")
|
||||||
|
providedRuntime("org.springframework.boot:spring-boot-starter-tomcat:{bootVersion}")
|
||||||
|
}
|
||||||
|
|
||||||
|
bootWar {
|
||||||
|
manifest {
|
||||||
|
attributes(
|
||||||
|
'Implementation-Version': '1.0.0',
|
||||||
|
'Implementation-Title': "Paketo Test"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,33 @@
|
|||||||
|
plugins {
|
||||||
|
id 'org.springframework.boot' version '{bootVersion}'
|
||||||
|
id 'io.spring.dependency-management' version '{dependencyManagementPluginVersion}'
|
||||||
|
id 'java'
|
||||||
|
id 'application'
|
||||||
|
}
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
maven { url 'https://repo.spring.io/milestone' }
|
||||||
|
maven { url 'https://repo.spring.io/snapshot' }
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation("org.springframework.boot:spring-boot-starter-web:{bootVersion}")
|
||||||
|
}
|
||||||
|
|
||||||
|
bootJar {
|
||||||
|
manifest {
|
||||||
|
attributes(
|
||||||
|
'Implementation-Version': '1.0.0',
|
||||||
|
'Implementation-Title': "Paketo Test"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
application {
|
||||||
|
mainClass = 'example.ExampleApplication'
|
||||||
|
}
|
||||||
|
|
||||||
|
bootBuildImage {
|
||||||
|
archiveFile = new File("${buildDir}/distributions/${project.name}.zip")
|
||||||
|
}
|
@ -0,0 +1,26 @@
|
|||||||
|
plugins {
|
||||||
|
id 'org.springframework.boot' version '{bootVersion}'
|
||||||
|
id 'io.spring.dependency-management' version '{dependencyManagementPluginVersion}'
|
||||||
|
id 'java'
|
||||||
|
id 'war'
|
||||||
|
}
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
maven { url 'https://repo.spring.io/milestone' }
|
||||||
|
maven { url 'https://repo.spring.io/snapshot' }
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation("org.springframework.boot:spring-boot-starter-web:{bootVersion}")
|
||||||
|
providedRuntime("org.springframework.boot:spring-boot-starter-tomcat:{bootVersion}")
|
||||||
|
}
|
||||||
|
|
||||||
|
war {
|
||||||
|
enabled = true
|
||||||
|
archiveClassifier.set('plain')
|
||||||
|
}
|
||||||
|
|
||||||
|
bootBuildImage {
|
||||||
|
archiveFile = war.archiveFile
|
||||||
|
}
|
@ -0,0 +1,24 @@
|
|||||||
|
plugins {
|
||||||
|
id 'org.springframework.boot' version '{bootVersion}'
|
||||||
|
id 'io.spring.dependency-management' version '{dependencyManagementPluginVersion}'
|
||||||
|
id 'java'
|
||||||
|
}
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
maven { url 'https://repo.spring.io/milestone' }
|
||||||
|
maven { url 'https://repo.spring.io/snapshot' }
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation("org.springframework.boot:spring-boot-starter-web:{bootVersion}")
|
||||||
|
}
|
||||||
|
|
||||||
|
bootJar {
|
||||||
|
manifest {
|
||||||
|
attributes(
|
||||||
|
'Implementation-Version': '1.0.0',
|
||||||
|
'Implementation-Title': "Paketo Test"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
pluginManagement {
|
||||||
|
repositories {
|
||||||
|
maven { url 'https://repo.spring.io/milestone' }
|
||||||
|
maven { url 'https://repo.spring.io/snapshot' }
|
||||||
|
gradlePluginPortal()
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue