mirror of
https://github.com/spring-projects/spring-boot.git
synced 2026-09-17 12:09:16 +00:00
Restructure project directories to better fit Gradle
Closes gh-46358
This commit is contained in:
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright 2012-present 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.
|
||||
*/
|
||||
|
||||
plugins {
|
||||
id "war"
|
||||
id "org.springframework.boot.system-test"
|
||||
}
|
||||
|
||||
description = "Spring Boot Deployment Tests"
|
||||
|
||||
configurations {
|
||||
providedRuntime {
|
||||
extendsFrom dependencyManagement
|
||||
}
|
||||
}
|
||||
|
||||
configurations.all {
|
||||
exclude module: "spring-boot-starter-logging"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly("jakarta.servlet:jakarta.servlet-api")
|
||||
|
||||
implementation(project(":starter:spring-boot-starter-web")) {
|
||||
exclude group: "org.hibernate.validator"
|
||||
exclude group: "org.springframework.boot", module: "spring-boot-starter-tomcat"
|
||||
}
|
||||
implementation(project(":starter:spring-boot-starter-actuator"))
|
||||
|
||||
systemTestImplementation(enforcedPlatform(project(path: ":platform:spring-boot-internal-dependencies")))
|
||||
systemTestImplementation(project(":starter:spring-boot-starter-test"))
|
||||
systemTestImplementation(project(":test-support:spring-boot-test-support"))
|
||||
systemTestImplementation("org.apache.httpcomponents.client5:httpclient5")
|
||||
systemTestImplementation("org.testcontainers:junit-jupiter")
|
||||
systemTestImplementation("org.testcontainers:testcontainers")
|
||||
systemTestImplementation("org.springframework:spring-web")
|
||||
}
|
||||
|
||||
systemTest {
|
||||
inputs.files(war).withNormalizer(ClasspathNormalizer).withPropertyName("war")
|
||||
}
|
||||
|
||||
war {
|
||||
archiveVersion = ''
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright 2012-present 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 sample.app;
|
||||
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
|
||||
|
||||
@SpringBootApplication
|
||||
public class DeploymentTestApplication extends SpringBootServletInitializer {
|
||||
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright 2012-present 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 sample.app;
|
||||
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
public class SampleController {
|
||||
|
||||
@GetMapping("/")
|
||||
public String hello() {
|
||||
return "Hello World";
|
||||
}
|
||||
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright 2012-present 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 sample.autoconfig;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnWarDeployment;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
|
||||
@ConditionalOnWarDeployment
|
||||
@AutoConfiguration
|
||||
public class ExampleAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
public TestEndpoint testEndpoint() {
|
||||
return new TestEndpoint();
|
||||
}
|
||||
|
||||
@Endpoint(id = "war")
|
||||
static class TestEndpoint {
|
||||
|
||||
@ReadOperation
|
||||
String hello() {
|
||||
return "{\"hello\":\"world\"}";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
sample.autoconfig.ExampleAutoConfiguration
|
||||
@@ -0,0 +1 @@
|
||||
management.endpoints.web.exposure.include: '*'
|
||||
+150
@@ -0,0 +1,150 @@
|
||||
/*
|
||||
* Copyright 2012-present 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.deployment;
|
||||
|
||||
import java.io.File;
|
||||
import java.time.Duration;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.apache.hc.client5.http.impl.DefaultHttpRequestRetryStrategy;
|
||||
import org.apache.hc.client5.http.impl.classic.HttpClients;
|
||||
import org.apache.hc.core5.util.TimeValue;
|
||||
import org.awaitility.Awaitility;
|
||||
import org.awaitility.core.ConditionTimeoutException;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.testcontainers.containers.GenericContainer;
|
||||
import org.testcontainers.images.builder.ImageFromDockerfile;
|
||||
import org.testcontainers.images.builder.dockerfile.DockerfileBuilder;
|
||||
|
||||
import org.springframework.boot.restclient.RestTemplateBuilder;
|
||||
import org.springframework.boot.web.server.test.client.TestRestTemplate;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Abstract class for deployment tests.
|
||||
*/
|
||||
abstract class AbstractDeploymentTests {
|
||||
|
||||
protected static final int DEFAULT_PORT = 8080;
|
||||
|
||||
@Test
|
||||
void home() {
|
||||
getDeployedApplication().test((rest) -> {
|
||||
ResponseEntity<String> response = rest.getForEntity("/", String.class);
|
||||
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
|
||||
assertThat(response.getBody()).isEqualTo("Hello World");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void health() {
|
||||
getDeployedApplication().test((rest) -> {
|
||||
ResponseEntity<String> response = rest.getForEntity("/actuator/health", String.class);
|
||||
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
|
||||
assertThat(response.getBody()).isEqualTo("{\"status\":\"UP\"}");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void conditionalOnWarShouldBeTrue() {
|
||||
getDeployedApplication().test((rest) -> {
|
||||
ResponseEntity<String> response = rest.getForEntity("/actuator/war", String.class);
|
||||
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
|
||||
assertThat(response.getBody()).isEqualTo("{\"hello\":\"world\"}");
|
||||
});
|
||||
}
|
||||
|
||||
private DeployedApplication getDeployedApplication() {
|
||||
return new DeployedApplication(getContainer(), getPort());
|
||||
}
|
||||
|
||||
protected int getPort() {
|
||||
return DEFAULT_PORT;
|
||||
}
|
||||
|
||||
abstract WarDeploymentContainer getContainer();
|
||||
|
||||
static final class DeployedApplication {
|
||||
|
||||
private final WarDeploymentContainer container;
|
||||
|
||||
private final int port;
|
||||
|
||||
DeployedApplication(WarDeploymentContainer container, int port) {
|
||||
this.container = container;
|
||||
this.port = port;
|
||||
}
|
||||
|
||||
private void test(Consumer<TestRestTemplate> consumer) {
|
||||
TestRestTemplate rest = new TestRestTemplate(new RestTemplateBuilder()
|
||||
.rootUri("http://" + this.container.getHost() + ":" + this.container.getMappedPort(this.port)
|
||||
+ "/spring-boot")
|
||||
.requestFactory(() -> new HttpComponentsClientHttpRequestFactory(HttpClients.custom()
|
||||
.setRetryStrategy(new DefaultHttpRequestRetryStrategy(10, TimeValue.of(1, TimeUnit.SECONDS)))
|
||||
.build())));
|
||||
try {
|
||||
Awaitility.await().atMost(Duration.ofMinutes(10)).until(() -> {
|
||||
try {
|
||||
System.out.println(this.container.getLogs());
|
||||
consumer.accept(rest);
|
||||
return true;
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (ConditionTimeoutException ex) {
|
||||
System.out.println(this.container.getLogs());
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static final class WarDeploymentContainer extends GenericContainer<WarDeploymentContainer> {
|
||||
|
||||
WarDeploymentContainer(String baseImage, String deploymentLocation, int port) {
|
||||
this(baseImage, deploymentLocation, port, null);
|
||||
}
|
||||
|
||||
WarDeploymentContainer(String baseImage, String deploymentLocation, int port,
|
||||
Consumer<DockerfileBuilder> dockerfileCustomizer) {
|
||||
super(new ImageFromDockerfile().withFileFromFile("spring-boot.war", findWarToDeploy())
|
||||
.withDockerfileFromBuilder((builder) -> {
|
||||
builder.from(baseImage).add("spring-boot.war", deploymentLocation + "/spring-boot.war");
|
||||
if (dockerfileCustomizer != null) {
|
||||
dockerfileCustomizer.accept(builder);
|
||||
}
|
||||
}));
|
||||
withExposedPorts(port).withStartupTimeout(Duration.ofMinutes(5)).withStartupAttempts(3);
|
||||
}
|
||||
|
||||
private static File findWarToDeploy() {
|
||||
File[] candidates = new File("build/libs").listFiles();
|
||||
assertThat(candidates).hasSize(1);
|
||||
return candidates[0];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright 2012-present 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.deployment;
|
||||
|
||||
import org.testcontainers.junit.jupiter.Container;
|
||||
import org.testcontainers.junit.jupiter.Testcontainers;
|
||||
|
||||
/**
|
||||
* Deployment tests for Open Liberty.
|
||||
*
|
||||
* @author Christoph Dreis
|
||||
* @author Scott Frederick
|
||||
*/
|
||||
@Testcontainers(disabledWithoutDocker = true)
|
||||
class OpenLibertyDeploymentTests extends AbstractDeploymentTests {
|
||||
|
||||
private static final int PORT = 9080;
|
||||
|
||||
@Container
|
||||
static WarDeploymentContainer container = new WarDeploymentContainer(
|
||||
"icr.io/appcafe/open-liberty:full-java17-openj9-ubi", "/config/dropins", PORT,
|
||||
(builder) -> builder.run("sed -i 's/javaee-8.0/jakartaee-10.0/g' /config/server.xml"));
|
||||
|
||||
@Override
|
||||
WarDeploymentContainer getContainer() {
|
||||
return container;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getPort() {
|
||||
return PORT;
|
||||
}
|
||||
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright 2012-present 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.deployment;
|
||||
|
||||
import org.testcontainers.junit.jupiter.Container;
|
||||
import org.testcontainers.junit.jupiter.Testcontainers;
|
||||
|
||||
/**
|
||||
* Deployment tests for TomEE.
|
||||
*
|
||||
* @author Christoph Dreis
|
||||
* @author Scott Frederick
|
||||
*/
|
||||
@Testcontainers(disabledWithoutDocker = true)
|
||||
class TomEEDeploymentTests extends AbstractDeploymentTests {
|
||||
|
||||
@Container
|
||||
static WarDeploymentContainer container = new WarDeploymentContainer("tomee:9.1.1-jre17-webprofile",
|
||||
"/usr/local/tomee/webapps", DEFAULT_PORT);
|
||||
|
||||
@Override
|
||||
WarDeploymentContainer getContainer() {
|
||||
return container;
|
||||
}
|
||||
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright 2012-present 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.deployment;
|
||||
|
||||
import org.testcontainers.junit.jupiter.Container;
|
||||
import org.testcontainers.junit.jupiter.Testcontainers;
|
||||
|
||||
/**
|
||||
* Deployment tests for Tomcat.
|
||||
*
|
||||
* @author Christoph Dreis
|
||||
* @author Scott Frederick
|
||||
*/
|
||||
@Testcontainers(disabledWithoutDocker = true)
|
||||
class TomcatDeploymentTests extends AbstractDeploymentTests {
|
||||
|
||||
@Container
|
||||
static WarDeploymentContainer container = new WarDeploymentContainer("tomcat:10.1.15-jdk17",
|
||||
"/usr/local/tomcat/webapps", DEFAULT_PORT);
|
||||
|
||||
@Override
|
||||
WarDeploymentContainer getContainer() {
|
||||
return container;
|
||||
}
|
||||
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright 2012-present 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.deployment;
|
||||
|
||||
import org.testcontainers.junit.jupiter.Container;
|
||||
import org.testcontainers.junit.jupiter.Testcontainers;
|
||||
|
||||
/**
|
||||
* Deployment tests for Wildfly.
|
||||
*
|
||||
* @author Christoph Dreis
|
||||
* @author Scott Frederick
|
||||
*/
|
||||
@Testcontainers(disabledWithoutDocker = true)
|
||||
class WildflyDeploymentTests extends AbstractDeploymentTests {
|
||||
|
||||
@Container
|
||||
static WarDeploymentContainer container = new WarDeploymentContainer("quay.io/wildfly/wildfly:27.0.0.Final-jdk17",
|
||||
"/opt/jboss/wildfly/standalone/deployments", DEFAULT_PORT);
|
||||
|
||||
@Override
|
||||
WarDeploymentContainer getContainer() {
|
||||
return container;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright 2012-present 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.
|
||||
*/
|
||||
|
||||
plugins {
|
||||
id 'java-gradle-plugin'
|
||||
id "org.springframework.boot.system-test"
|
||||
}
|
||||
|
||||
description = "Spring Boot Image Building Tests"
|
||||
|
||||
configurations {
|
||||
app
|
||||
providedRuntime {
|
||||
extendsFrom dependencyManagement
|
||||
}
|
||||
}
|
||||
|
||||
tasks.register("syncMavenRepository", Sync) {
|
||||
from configurations.app
|
||||
into layout.buildDirectory.dir("system-test-maven-repository")
|
||||
}
|
||||
|
||||
systemTest {
|
||||
dependsOn syncMavenRepository
|
||||
if (project.hasProperty("springBootVersion")) {
|
||||
systemProperty "springBootVersion", project.properties["springBootVersion"]
|
||||
} else {
|
||||
systemProperty "springBootVersion", project.getVersion()
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
app project(path: ":build-plugin:spring-boot-gradle-plugin", configuration: "mavenRepository")
|
||||
app project(path: ":starter:spring-boot-starter-web", configuration: "mavenRepository")
|
||||
|
||||
implementation(project(":starter:spring-boot-starter-web")) {
|
||||
exclude group: "org.hibernate.validator"
|
||||
}
|
||||
|
||||
systemTestImplementation(project(":starter:spring-boot-starter-test"))
|
||||
systemTestImplementation(project(":test-support:spring-boot-gradle-test-support"))
|
||||
systemTestImplementation(project(":buildpack:spring-boot-buildpack-platform"))
|
||||
systemTestImplementation(gradleTestKit())
|
||||
systemTestImplementation("org.assertj:assertj-core")
|
||||
systemTestImplementation("org.testcontainers:junit-jupiter")
|
||||
systemTestImplementation("org.testcontainers:testcontainers")
|
||||
}
|
||||
|
||||
toolchain {
|
||||
maximumCompatibleJavaVersion = JavaLanguageVersion.of(23)
|
||||
}
|
||||
+149
@@ -0,0 +1,149 @@
|
||||
/*
|
||||
* Copyright 2012-present 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.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import com.github.dockerjava.api.model.ContainerConfig;
|
||||
import org.assertj.core.api.AbstractAssert;
|
||||
import org.assertj.core.api.AbstractListAssert;
|
||||
import org.assertj.core.api.AbstractMapAssert;
|
||||
import org.assertj.core.api.AbstractObjectAssert;
|
||||
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 void buildMetadata(Consumer<BuildMetadataAssert> assertConsumer) {
|
||||
assertConsumer.accept(new BuildMetadataAssert(jsonLabel(BUILD_METADATA_LABEL)));
|
||||
}
|
||||
|
||||
public void lifecycleMetadata(Consumer<LifecycleMetadataAssert> assertConsumer) {
|
||||
assertConsumer.accept(new LifecycleMetadataAssert(jsonLabel(LIFECYCLE_METADATA_LABEL)));
|
||||
}
|
||||
|
||||
public void labels(Consumer<LabelsAssert> assertConsumer) {
|
||||
assertConsumer.accept(new LabelsAssert(this.actual.getLabels()));
|
||||
}
|
||||
|
||||
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 labels on an image.
|
||||
*/
|
||||
public static class LabelsAssert extends AbstractMapAssert<LabelsAssert, Map<String, String>, String, String> {
|
||||
|
||||
protected LabelsAssert(Map<String, String> labels) {
|
||||
super(labels, LabelsAssert.class);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 AbstractListAssert<?, List<? extends String>, String, ObjectAssert<String>> processOfType(String type) {
|
||||
return this.actual.extractingJsonPathArrayValue("$.processes[?(@.type=='%s')]", type)
|
||||
.singleElement()
|
||||
.extracting("command", "args")
|
||||
.flatMap(this::getArgs);
|
||||
}
|
||||
|
||||
private Collection<String> getArgs(Object obj) {
|
||||
if (obj instanceof List<?> list) {
|
||||
return list.stream().map(Objects::toString).toList();
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts for 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");
|
||||
}
|
||||
|
||||
public AbstractObjectAssert<?, Object> sbomLayerSha() {
|
||||
return this.actual.extractingJsonPathValue("$.sbom.sha");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+124
@@ -0,0 +1,124 @@
|
||||
/*
|
||||
* Copyright 2012-present 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 java.util.function.Consumer;
|
||||
|
||||
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;
|
||||
import org.springframework.boot.test.json.JsonContentAssert;
|
||||
import org.springframework.util.StreamUtils;
|
||||
|
||||
/**
|
||||
* 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 void layer(String layerDigest, Consumer<LayerContentAssert> assertConsumer) {
|
||||
if (!this.layers.containsKey(layerDigest)) {
|
||||
failWithMessage("Layer with digest '%s' not found in image", layerDigest);
|
||||
}
|
||||
assertConsumer.accept(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() {
|
||||
List<String> entryNames = new ArrayList<>();
|
||||
try {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
this.actual.writeTo(out);
|
||||
try (TarArchiveInputStream in = new TarArchiveInputStream(
|
||||
new ByteArrayInputStream(out.toByteArray()))) {
|
||||
TarArchiveEntry entry = in.getNextEntry();
|
||||
while (entry != null) {
|
||||
if (!entry.isDirectory()) {
|
||||
entryNames.add(entry.getName().replaceFirst("^/workspace/", ""));
|
||||
}
|
||||
entry = in.getNextEntry();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (IOException ex) {
|
||||
failWithMessage("IOException while reading image layer archive: '%s'", ex.getMessage());
|
||||
}
|
||||
return Assertions.assertThat(entryNames);
|
||||
}
|
||||
|
||||
public void jsonEntry(String name, Consumer<JsonContentAssert> assertConsumer) {
|
||||
try {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
this.actual.writeTo(out);
|
||||
try (TarArchiveInputStream in = new TarArchiveInputStream(
|
||||
new ByteArrayInputStream(out.toByteArray()))) {
|
||||
TarArchiveEntry entry = in.getNextEntry();
|
||||
while (entry != null) {
|
||||
if (entry.getName().equals(name)) {
|
||||
ByteArrayOutputStream entryOut = new ByteArrayOutputStream();
|
||||
StreamUtils.copy(in, entryOut);
|
||||
assertConsumer.accept(new JsonContentAssert(LayerContentAssert.class, entryOut.toString()));
|
||||
return;
|
||||
}
|
||||
entry = in.getNextEntry();
|
||||
}
|
||||
}
|
||||
failWithMessage("Expected JSON entry '%s' in layer with digest '%s'", name, this.actual.getId());
|
||||
}
|
||||
catch (IOException ex) {
|
||||
failWithMessage("IOException while reading image layer archive: '%s'", ex.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright 2012-present 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);
|
||||
}
|
||||
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright 2012-present 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.minimumCompatible());
|
||||
String bootVersion = System.getProperty("springBootVersion");
|
||||
Assert.state(bootVersion != null, "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);
|
||||
}
|
||||
|
||||
}
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright 2012-present 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.LoaderOptions;
|
||||
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, getLoaderOptions()));
|
||||
return yaml.load(jarFile.getInputStream(indexEntry));
|
||||
}
|
||||
}
|
||||
|
||||
private static LoaderOptions getLoaderOptions() {
|
||||
LoaderOptions loaderOptions = new LoaderOptions();
|
||||
loaderOptions.setAllowDuplicateKeys(false);
|
||||
loaderOptions.setMaxAliasesForCollections(Integer.MAX_VALUE);
|
||||
loaderOptions.setAllowRecursiveKeys(true);
|
||||
return loaderOptions;
|
||||
}
|
||||
|
||||
}
|
||||
+613
@@ -0,0 +1,613 @@
|
||||
/*
|
||||
* Copyright 2012-present 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.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
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.BeforeEach;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
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.image.assertions.ImageAssertions;
|
||||
import org.springframework.boot.image.junit.GradleBuildInjectionExtension;
|
||||
import org.springframework.boot.testsupport.gradle.testkit.GradleBuild;
|
||||
import org.springframework.boot.testsupport.gradle.testkit.GradleBuildExtension;
|
||||
import org.springframework.boot.testsupport.gradle.testkit.GradleVersions;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.entry;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
@BeforeEach
|
||||
void configureGradleBuild() {
|
||||
this.gradleBuild.scriptProperty("systemTestMavenRepository",
|
||||
new File("build/system-test-maven-repository").getAbsoluteFile().toURI().toASCIIString());
|
||||
this.gradleBuild.scriptPropertyFrom(new File("../../gradle.properties"), "nativeBuildToolsVersion");
|
||||
this.gradleBuild.expectDeprecationMessages("BPL_SPRING_CLOUD_BINDINGS_ENABLED.*true.*Deprecated");
|
||||
this.gradleBuild.expectDeprecationMessages("Command \"packages\" is deprecated, use `syft scan` instead");
|
||||
this.gradleBuild.expectDeprecationMessages("BP_ENABLE_RUNTIME_CERT_BINDING.*true.*Deprecated");
|
||||
this.gradleBuild.gradleVersion(GradleVersions.maximumCompatible());
|
||||
}
|
||||
|
||||
@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);
|
||||
assertThat(result.getOutput()).contains("Running creator");
|
||||
try (GenericContainer<?> container = new GenericContainer<>(imageName)) {
|
||||
container.withExposedPorts(8080);
|
||||
container.waitingFor(Wait.forHttp("/test")).start();
|
||||
ContainerConfig config = container.getContainerInfo().getConfig();
|
||||
assertLabelsMatchManifestAttributes(config);
|
||||
ImageAssertions.assertThat(config).buildMetadata((metadata) -> {
|
||||
metadata.buildpacks()
|
||||
.contains("paketo-buildpacks/ca-certificates", "paketo-buildpacks/bellsoft-liberica",
|
||||
"paketo-buildpacks/executable-jar", "paketo-buildpacks/dist-zip",
|
||||
"paketo-buildpacks/spring-boot");
|
||||
metadata.processOfType("web")
|
||||
.containsExactly("java", "org.springframework.boot.loader.launch.JarLauncher");
|
||||
metadata.processOfType("executable-jar")
|
||||
.containsExactly("java", "org.springframework.boot.loader.launch.JarLauncher");
|
||||
});
|
||||
assertImageHasJvmSbomLayer(imageReference, config);
|
||||
assertImageHasDependenciesSbomLayer(imageReference, config, "executable-jar");
|
||||
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);
|
||||
assertThat(result.getOutput()).contains("Running creator");
|
||||
try (GenericContainer<?> container = new GenericContainer<>(imageName)) {
|
||||
container.withCommand("--server.port=9090");
|
||||
container.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);
|
||||
assertThat(result.getOutput()).contains("Running creator");
|
||||
try (GenericContainer<?> container = new GenericContainer<>(imageName)) {
|
||||
container.withExposedPorts(8080);
|
||||
container.waitingFor(Wait.forHttp("/test")).start();
|
||||
container.stop();
|
||||
}
|
||||
this.gradleBuild.expectDeprecationMessages("BOM table is deprecated");
|
||||
result = buildImage(imageName);
|
||||
assertThat(result.task(":bootBuildImage").getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
|
||||
try (GenericContainer<?> container = new GenericContainer<>(imageName)) {
|
||||
container.withExposedPorts(8080);
|
||||
container.waitingFor(Wait.forHttp("/test")).start();
|
||||
}
|
||||
finally {
|
||||
removeImage(imageReference);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled("0.4.292 of the builder launches an unpacked jar rather than the script in bin")
|
||||
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);
|
||||
assertThat(result.getOutput()).contains("Running creator");
|
||||
try (GenericContainer<?> container = new GenericContainer<>(imageName)) {
|
||||
container.withExposedPorts(8080);
|
||||
container.waitingFor(Wait.forHttp("/test")).start();
|
||||
ContainerConfig config = container.getContainerInfo().getConfig();
|
||||
ImageAssertions.assertThat(config).buildMetadata((metadata) -> {
|
||||
metadata.buildpacks()
|
||||
.contains("paketo-buildpacks/ca-certificates", "paketo-buildpacks/bellsoft-liberica",
|
||||
"paketo-buildpacks/dist-zip", "paketo-buildpacks/spring-boot");
|
||||
String launcher = "/workspace/" + projectName + "-boot/bin/" + projectName;
|
||||
metadata.processOfType("web").containsExactly(launcher);
|
||||
metadata.processOfType("dist-zip").containsExactly(launcher);
|
||||
});
|
||||
assertImageHasJvmSbomLayer(imageReference, config);
|
||||
assertImageHasDependenciesSbomLayer(imageReference, config, "dist-zip");
|
||||
DigestCapturingCondition digest = new DigestCapturingCondition();
|
||||
ImageAssertions.assertThat(config)
|
||||
.lifecycleMetadata((metadata) -> metadata.appLayerShas().haveExactly(1, digest));
|
||||
ImageAssertions.assertThat(imageReference)
|
||||
.layer(digest.getDigest(),
|
||||
(layer) -> layer.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);
|
||||
assertThat(result.getOutput()).contains("Running creator");
|
||||
try (GenericContainer<?> container = new GenericContainer<>(imageName)) {
|
||||
container.withExposedPorts(8080);
|
||||
container.waitingFor(Wait.forHttp("/test")).start();
|
||||
ContainerConfig config = container.getContainerInfo().getConfig();
|
||||
ImageAssertions.assertThat(config).buildMetadata((metadata) -> {
|
||||
metadata.buildpacks()
|
||||
.contains("paketo-buildpacks/ca-certificates", "paketo-buildpacks/bellsoft-liberica",
|
||||
"paketo-buildpacks/dist-zip", "paketo-buildpacks/spring-boot");
|
||||
String launcher = "/workspace/" + projectName + "/bin/" + projectName;
|
||||
metadata.processOfType("web").containsExactly(launcher);
|
||||
metadata.processOfType("dist-zip").containsExactly(launcher);
|
||||
});
|
||||
assertImageHasJvmSbomLayer(imageReference, config);
|
||||
assertImageHasDependenciesSbomLayer(imageReference, config, "dist-zip");
|
||||
DigestCapturingCondition digest = new DigestCapturingCondition();
|
||||
ImageAssertions.assertThat(config)
|
||||
.lifecycleMetadata((metadata) -> metadata.appLayerShas().haveExactly(1, digest));
|
||||
ImageAssertions.assertThat(imageReference)
|
||||
.layer(digest.getDigest(), (layer) -> layer.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);
|
||||
assertThat(result.getOutput()).contains("Running creator");
|
||||
try (GenericContainer<?> container = new GenericContainer<>(imageName)) {
|
||||
container.withExposedPorts(8080);
|
||||
container.waitingFor(Wait.forHttp("/test")).start();
|
||||
ContainerConfig config = container.getContainerInfo().getConfig();
|
||||
assertLabelsMatchManifestAttributes(config);
|
||||
ImageAssertions.assertThat(config).buildMetadata((metadata) -> {
|
||||
metadata.buildpacks()
|
||||
.contains("paketo-buildpacks/ca-certificates", "paketo-buildpacks/bellsoft-liberica",
|
||||
"paketo-buildpacks/executable-jar", "paketo-buildpacks/dist-zip",
|
||||
"paketo-buildpacks/spring-boot");
|
||||
metadata.processOfType("web")
|
||||
.containsExactly("java", "org.springframework.boot.loader.launch.WarLauncher");
|
||||
metadata.processOfType("executable-jar")
|
||||
.containsExactly("java", "org.springframework.boot.loader.launch.WarLauncher");
|
||||
});
|
||||
assertImageHasJvmSbomLayer(imageReference, config);
|
||||
assertImageHasDependenciesSbomLayer(imageReference, config, "executable-jar");
|
||||
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 = buildImageWithRetry(imageName);
|
||||
assertThat(result.task(":bootBuildImage").getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
|
||||
assertThat(result.getOutput()).contains("Running creator");
|
||||
try (GenericContainer<?> container = new GenericContainer<>(imageName)) {
|
||||
container.withExposedPorts(8080);
|
||||
container.waitingFor(Wait.forHttp("/test")).start();
|
||||
ContainerConfig config = container.getContainerInfo().getConfig();
|
||||
ImageAssertions.assertThat(config).buildMetadata((metadata) -> {
|
||||
metadata.buildpacks()
|
||||
.contains("paketo-buildpacks/ca-certificates", "paketo-buildpacks/bellsoft-liberica",
|
||||
"paketo-buildpacks/apache-tomcat", "paketo-buildpacks/dist-zip",
|
||||
"paketo-buildpacks/spring-boot");
|
||||
metadata.processOfType("web")
|
||||
.containsSubsequence("sh", "/layers/paketo-buildpacks_apache-tomcat/tomcat/bin/catalina.sh", "run");
|
||||
metadata.processOfType("tomcat")
|
||||
.containsSubsequence("sh", "/layers/paketo-buildpacks_apache-tomcat/tomcat/bin/catalina.sh", "run");
|
||||
});
|
||||
assertImageHasJvmSbomLayer(imageReference, config);
|
||||
assertImageHasDependenciesSbomLayer(imageReference, config, "apache-tomcat");
|
||||
DigestCapturingCondition digest = new DigestCapturingCondition();
|
||||
ImageAssertions.assertThat(config)
|
||||
.lifecycleMetadata((metadata) -> metadata.appLayerShas().haveExactly(1, digest));
|
||||
ImageAssertions.assertThat(imageReference)
|
||||
.layer(digest.getDigest(),
|
||||
(layer) -> layer.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);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void nativeApp() throws Exception {
|
||||
this.gradleBuild.expectDeprecationMessages("uses or overrides a deprecated API");
|
||||
this.gradleBuild.expectDeprecationMessages("has been deprecated and marked for removal");
|
||||
// these deprecations are transitive from the Native Build Tools Gradle plugin
|
||||
this.gradleBuild
|
||||
.expectDeprecationMessages("has been deprecated. This is scheduled to be removed in Gradle 9.0");
|
||||
this.gradleBuild.expectDeprecationMessages("upgrading_version_8.html#deprecated_access_to_convention");
|
||||
// these deprecations are from native image buildpacks
|
||||
this.gradleBuild.expectDeprecationMessages("Using a deprecated option --report-unsupported-elements-at-runtime",
|
||||
"The option is deprecated and will be removed in the future.");
|
||||
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);
|
||||
assertThat(result.getOutput()).contains("Running creator");
|
||||
try (GenericContainer<?> container = new GenericContainer<>(imageName)) {
|
||||
container.withExposedPorts(8080);
|
||||
container.waitingFor(Wait.forHttp("/test")).start();
|
||||
ContainerConfig config = container.getContainerInfo().getConfig();
|
||||
assertLabelsMatchManifestAttributes(config);
|
||||
ImageAssertions.assertThat(config).buildMetadata((metadata) -> {
|
||||
metadata.buildpacks()
|
||||
.contains("paketo-buildpacks/ca-certificates", "paketo-buildpacks/bellsoft-liberica",
|
||||
"paketo-buildpacks/executable-jar", "paketo-buildpacks/spring-boot",
|
||||
"paketo-buildpacks/native-image");
|
||||
metadata.processOfType("web")
|
||||
.satisfiesExactly((command) -> assertThat(command).endsWith("/example.ExampleApplication"));
|
||||
metadata.processOfType("native-image")
|
||||
.satisfiesExactly((command) -> assertThat(command).endsWith("/example.ExampleApplication"));
|
||||
});
|
||||
assertImageHasDependenciesSbomLayer(imageReference, config, "native-image");
|
||||
}
|
||||
finally {
|
||||
removeImage(imageReference);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void classDataSharingApp() 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);
|
||||
assertThat(result.getOutput()).contains("Running creator");
|
||||
try (GenericContainer<?> container = new GenericContainer<>(imageName)) {
|
||||
container.withExposedPorts(8080);
|
||||
container.waitingFor(Wait.forHttp("/test")).start();
|
||||
ContainerConfig config = container.getContainerInfo().getConfig();
|
||||
assertLabelsMatchManifestAttributes(config);
|
||||
ImageAssertions.assertThat(config).buildMetadata((metadata) -> {
|
||||
metadata.buildpacks()
|
||||
.contains("paketo-buildpacks/ca-certificates", "paketo-buildpacks/bellsoft-liberica",
|
||||
"paketo-buildpacks/executable-jar", "paketo-buildpacks/dist-zip",
|
||||
"paketo-buildpacks/spring-boot");
|
||||
metadata.processOfType("web")
|
||||
.satisfiesExactly((command) -> assertThat(command).isEqualTo("java"),
|
||||
(arg) -> assertThat(arg).isEqualTo("-cp"),
|
||||
(arg) -> assertThat(arg).startsWith("runner.jar"),
|
||||
(arg) -> assertThat(arg).isEqualTo("example.ExampleApplication"));
|
||||
metadata.processOfType("spring-boot-app")
|
||||
.satisfiesExactly((command) -> assertThat(command).isEqualTo("java"),
|
||||
(arg) -> assertThat(arg).isEqualTo("-cp"),
|
||||
(arg) -> assertThat(arg).startsWith("runner.jar"),
|
||||
(arg) -> assertThat(arg).isEqualTo("example.ExampleApplication"));
|
||||
metadata.processOfType("executable-jar")
|
||||
.containsExactly("java", "org.springframework.boot.loader.launch.JarLauncher");
|
||||
});
|
||||
assertImageHasJvmSbomLayer(imageReference, config);
|
||||
assertImageHasDependenciesSbomLayer(imageReference, config, "executable-jar");
|
||||
}
|
||||
finally {
|
||||
removeImage(imageReference);
|
||||
}
|
||||
}
|
||||
|
||||
private BuildResult buildImageWithRetry(String imageName, String... arguments) {
|
||||
long start = System.nanoTime();
|
||||
while (true) {
|
||||
try {
|
||||
return buildImage(imageName, arguments);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
ex.printStackTrace();
|
||||
if (Duration.ofNanos(System.nanoTime() - start).toMinutes() > 6) {
|
||||
throw ex;
|
||||
}
|
||||
sleep(500);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void sleep(long time) {
|
||||
try {
|
||||
Thread.sleep(time);
|
||||
}
|
||||
catch (InterruptedException ex) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
private BuildResult buildImage(String imageName, String... arguments) {
|
||||
List<String> args = new ArrayList<>(List.of(arguments));
|
||||
args.add("bootBuildImage");
|
||||
args.add("--imageName=" + imageName);
|
||||
args.add("--pullPolicy=IF_NOT_PRESENT");
|
||||
return this.gradleBuild.build(args.toArray(new String[0]));
|
||||
}
|
||||
|
||||
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 {
|
||||
try (JarFile jarFile = new JarFile(projectArchiveFile())) {
|
||||
Attributes attributes = jarFile.getManifest().getMainAttributes();
|
||||
ImageAssertions.assertThat(config).labels((labels) -> {
|
||||
labels.contains(entry("org.springframework.boot.version", attributes.getValue("Spring-Boot-Version")));
|
||||
labels.contains(entry("org.opencontainers.image.title",
|
||||
attributes.getValue(Attributes.Name.IMPLEMENTATION_TITLE)));
|
||||
labels.contains(entry("org.opencontainers.image.version",
|
||||
attributes.getValue(Attributes.Name.IMPLEMENTATION_VERSION)));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void assertImageHasJvmSbomLayer(ImageReference imageReference, ContainerConfig config) throws IOException {
|
||||
DigestCapturingCondition digest = new DigestCapturingCondition();
|
||||
ImageAssertions.assertThat(config).lifecycleMetadata((metadata) -> metadata.sbomLayerSha().has(digest));
|
||||
ImageAssertions.assertThat(imageReference).layer(digest.getDigest(), (layer) -> {
|
||||
layer.entries().contains("/layers/sbom/launch/paketo-buildpacks_bellsoft-liberica/jre/sbom.syft.json");
|
||||
layer.jsonEntry("/layers/sbom/launch/paketo-buildpacks_bellsoft-liberica/jre/sbom.syft.json", (json) -> {
|
||||
json.extractingJsonPathStringValue("$.Artifacts[0].Name").isEqualTo("BellSoft Liberica JRE");
|
||||
json.extractingJsonPathStringValue("$.Artifacts[0].Version").startsWith(javaMajorVersion());
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private void assertImageHasDependenciesSbomLayer(ImageReference imageReference, ContainerConfig config,
|
||||
String buildpack) throws IOException {
|
||||
DigestCapturingCondition digest = new DigestCapturingCondition();
|
||||
ImageAssertions.assertThat(config).lifecycleMetadata((metadata) -> metadata.sbomLayerSha().has(digest));
|
||||
ImageAssertions.assertThat(imageReference).layer(digest.getDigest(), (layer) -> {
|
||||
layer.entries()
|
||||
.contains("/layers/sbom/launch/paketo-buildpacks_" + buildpack + "/sbom.syft.json",
|
||||
"/layers/sbom/launch/paketo-buildpacks_" + buildpack + "/sbom.cdx.json");
|
||||
layer.jsonEntry("/layers/sbom/launch/paketo-buildpacks_" + buildpack + "/sbom.syft.json",
|
||||
(json) -> json.extractingJsonPathArrayValue("$.artifacts.[*].name")
|
||||
.contains("commons-logging", "spring-beans", "spring-boot", "spring-boot-autoconfigure",
|
||||
"spring-context", "spring-core", "spring-expression", "spring-web", "spring-webmvc"));
|
||||
layer.jsonEntry("/layers/sbom/launch/paketo-buildpacks_" + buildpack + "/sbom.cdx.json",
|
||||
(json) -> json.extractingJsonPathArrayValue("$.components.[*].name")
|
||||
.contains("commons-logging", "spring-beans", "spring-boot", "spring-boot-autoconfigure",
|
||||
"spring-context", "spring-core", "spring-expression", "spring-web", "spring-webmvc"));
|
||||
});
|
||||
}
|
||||
|
||||
private void assertImageLayersMatchLayersIndex(ImageReference imageReference, ContainerConfig config)
|
||||
throws IOException {
|
||||
DigestsCapturingCondition digests = new DigestsCapturingCondition();
|
||||
ImageAssertions.assertThat(config)
|
||||
.lifecycleMetadata((metadata) -> metadata.appLayerShas().haveExactly(5, digests));
|
||||
LayersIndex layersIndex = LayersIndex.fromArchiveFile(projectArchiveFile());
|
||||
ImageAssertions.assertThat(imageReference)
|
||||
.layer(digests.getDigest(0), (layer) -> layer.entries()
|
||||
.allMatch((entry) -> startsWithOneOf(entry, layersIndex.getLayer("dependencies"))));
|
||||
ImageAssertions.assertThat(imageReference)
|
||||
.layer(digests.getDigest(1), (layer) -> layer.entries()
|
||||
.allMatch((entry) -> startsWithOneOf(entry, layersIndex.getLayer("spring-boot-loader"))));
|
||||
ImageAssertions.assertThat(imageReference)
|
||||
.layer(digests.getDigest(2), (layer) -> layer.entries()
|
||||
.allMatch((entry) -> startsWithOneOf(entry, layersIndex.getLayer("snapshot-dependencies"))));
|
||||
ImageAssertions.assertThat(imageReference)
|
||||
.layer(digests.getDigest(3), (layer) -> layer.entries()
|
||||
.allMatch((entry) -> startsWithOneOf(entry, layersIndex.getLayer("application"))));
|
||||
ImageAssertions.assertThat(imageReference)
|
||||
.layer(digests.getDigest(4),
|
||||
(layer) -> layer.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);
|
||||
}
|
||||
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 String digest = null;
|
||||
|
||||
DigestCapturingCondition() {
|
||||
super(predicate(), "a value starting with 'sha256:'");
|
||||
}
|
||||
|
||||
private static Predicate<Object> predicate() {
|
||||
return (sha) -> {
|
||||
digest = sha.toString();
|
||||
return sha.toString().startsWith("sha256:");
|
||||
};
|
||||
}
|
||||
|
||||
String getDigest() {
|
||||
return digest;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class DigestsCapturingCondition extends Condition<Object> {
|
||||
|
||||
private static List<String> digests;
|
||||
|
||||
DigestsCapturingCondition() {
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright 2012-present 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.
|
||||
*/
|
||||
|
||||
plugins {
|
||||
id 'org.springframework.boot' version '{bootVersion}'
|
||||
id 'io.spring.dependency-management' version '{dependencyManagementPluginVersion}'
|
||||
id 'java'
|
||||
id 'application'
|
||||
}
|
||||
|
||||
repositories {
|
||||
exclusiveContent {
|
||||
forRepository {
|
||||
maven {
|
||||
url = '{systemTestMavenRepository}'
|
||||
}
|
||||
}
|
||||
filter {
|
||||
includeGroup "org.springframework.boot"
|
||||
}
|
||||
}
|
||||
mavenCentral()
|
||||
spring.mavenRepositories()
|
||||
}
|
||||
|
||||
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 = bootDistZip.archiveFile
|
||||
environment = ['BP_JVM_VERSION': java.targetCompatibility.getMajorVersion()]
|
||||
}
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright 2012-present 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.
|
||||
*/
|
||||
|
||||
plugins {
|
||||
id 'org.springframework.boot' version '{bootVersion}'
|
||||
id 'io.spring.dependency-management' version '{dependencyManagementPluginVersion}'
|
||||
id 'java'
|
||||
}
|
||||
|
||||
repositories {
|
||||
exclusiveContent {
|
||||
forRepository {
|
||||
maven {
|
||||
url = '{systemTestMavenRepository}'
|
||||
}
|
||||
}
|
||||
filter {
|
||||
includeGroup "org.springframework.boot"
|
||||
}
|
||||
}
|
||||
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"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
bootBuildImage {
|
||||
environment = ['BP_JVM_CDS_ENABLED': 'true']
|
||||
}
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright 2012-present 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.
|
||||
*/
|
||||
|
||||
plugins {
|
||||
id 'org.springframework.boot' version '{bootVersion}'
|
||||
id 'io.spring.dependency-management' version '{dependencyManagementPluginVersion}'
|
||||
id 'java'
|
||||
id 'war'
|
||||
}
|
||||
|
||||
repositories {
|
||||
exclusiveContent {
|
||||
forRepository {
|
||||
maven {
|
||||
url = '{systemTestMavenRepository}'
|
||||
}
|
||||
}
|
||||
filter {
|
||||
includeGroup "org.springframework.boot"
|
||||
}
|
||||
}
|
||||
mavenCentral()
|
||||
spring.mavenRepositories()
|
||||
}
|
||||
|
||||
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"
|
||||
)
|
||||
}
|
||||
}
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright 2012-present 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.
|
||||
*/
|
||||
|
||||
plugins {
|
||||
id 'org.springframework.boot' version '{bootVersion}'
|
||||
id 'org.springframework.boot.aot' version '{bootVersion}'
|
||||
id 'io.spring.dependency-management' version '{dependencyManagementPluginVersion}'
|
||||
id 'org.graalvm.buildtools.native' version '{nativeBuildToolsVersion}'
|
||||
id 'java'
|
||||
}
|
||||
|
||||
repositories {
|
||||
exclusiveContent {
|
||||
forRepository {
|
||||
maven {
|
||||
url = '{systemTestMavenRepository}'
|
||||
}
|
||||
}
|
||||
filter {
|
||||
includeGroup "org.springframework.boot"
|
||||
}
|
||||
}
|
||||
mavenCentral()
|
||||
spring.mavenRepositories()
|
||||
}
|
||||
|
||||
tasks.named("bootBuildImage") {
|
||||
environment["BP_JVM_VERSION"] = "24"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("org.springframework.boot:spring-boot-starter-web:{bootVersion}")
|
||||
}
|
||||
|
||||
bootJar {
|
||||
manifest {
|
||||
attributes(
|
||||
'Implementation-Version': '1.0.0',
|
||||
'Implementation-Title': 'Paketo Test'
|
||||
)
|
||||
}
|
||||
}
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright 2012-present 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.
|
||||
*/
|
||||
|
||||
plugins {
|
||||
id 'org.springframework.boot' version '{bootVersion}'
|
||||
id 'io.spring.dependency-management' version '{dependencyManagementPluginVersion}'
|
||||
id 'java'
|
||||
id 'application'
|
||||
}
|
||||
|
||||
repositories {
|
||||
exclusiveContent {
|
||||
forRepository {
|
||||
maven {
|
||||
url = '{systemTestMavenRepository}'
|
||||
}
|
||||
}
|
||||
filter {
|
||||
includeGroup "org.springframework.boot"
|
||||
}
|
||||
}
|
||||
mavenCentral()
|
||||
spring.mavenRepositories()
|
||||
}
|
||||
|
||||
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 = distZip.archiveFile
|
||||
runImage = "paketobuildpacks/ubuntu-noble-run-base:latest"
|
||||
environment = ['BP_JVM_VERSION': java.targetCompatibility.getMajorVersion()]
|
||||
}
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright 2012-present 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.
|
||||
*/
|
||||
|
||||
plugins {
|
||||
id 'org.springframework.boot' version '{bootVersion}'
|
||||
id 'io.spring.dependency-management' version '{dependencyManagementPluginVersion}'
|
||||
id 'java'
|
||||
id 'war'
|
||||
}
|
||||
|
||||
repositories {
|
||||
exclusiveContent {
|
||||
forRepository {
|
||||
maven {
|
||||
url = '{systemTestMavenRepository}'
|
||||
}
|
||||
}
|
||||
filter {
|
||||
includeGroup "org.springframework.boot"
|
||||
}
|
||||
}
|
||||
mavenCentral()
|
||||
spring.mavenRepositories()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("org.springframework.boot:spring-boot-starter-web:{bootVersion}")
|
||||
}
|
||||
|
||||
war {
|
||||
enabled = true
|
||||
archiveClassifier.set('plain')
|
||||
}
|
||||
|
||||
bootBuildImage {
|
||||
archiveFile = war.archiveFile
|
||||
runImage = "paketobuildpacks/ubuntu-noble-run-base:latest"
|
||||
environment = ['BP_JVM_VERSION': java.targetCompatibility.getMajorVersion(), 'BP_TOMCAT_VERSION': '10.*']
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright 2012-present 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.
|
||||
*/
|
||||
|
||||
plugins {
|
||||
id 'org.springframework.boot' version '{bootVersion}'
|
||||
id 'io.spring.dependency-management' version '{dependencyManagementPluginVersion}'
|
||||
id 'java'
|
||||
}
|
||||
|
||||
repositories {
|
||||
exclusiveContent {
|
||||
forRepository {
|
||||
maven {
|
||||
url = '{systemTestMavenRepository}'
|
||||
}
|
||||
}
|
||||
filter {
|
||||
includeGroup "org.springframework.boot"
|
||||
}
|
||||
}
|
||||
mavenCentral()
|
||||
spring.mavenRepositories()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("org.springframework.boot:spring-boot-starter-web:{bootVersion}")
|
||||
}
|
||||
|
||||
bootJar {
|
||||
manifest {
|
||||
attributes(
|
||||
'Implementation-Version': '1.0.0',
|
||||
'Implementation-Title': "Paketo Test"
|
||||
)
|
||||
}
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright 2012-present 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.
|
||||
*/
|
||||
|
||||
pluginManagement {
|
||||
evaluate(new File("{parentRootDir}/buildSrc/SpringRepositorySupport.groovy")).apply(this)
|
||||
repositories {
|
||||
exclusiveContent {
|
||||
forRepository {
|
||||
maven {
|
||||
url = '{systemTestMavenRepository}'
|
||||
}
|
||||
}
|
||||
filter {
|
||||
includeGroup "org.springframework.boot"
|
||||
}
|
||||
}
|
||||
spring.mavenRepositories()
|
||||
gradlePluginPortal()
|
||||
}
|
||||
resolutionStrategy {
|
||||
eachPlugin {
|
||||
if (requested.id.id.startsWith("org.springframework.boot")) {
|
||||
useModule "org.springframework.boot:spring-boot-gradle-plugin:${requested.version}"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user