Add support for building OCI images using the Gradle plugin
Closes gh-19831
This commit is contained in:
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* Copyright 2012-2020 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.gradle.tasks.bundling;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
|
||||
import org.gradle.testkit.runner.BuildResult;
|
||||
import org.gradle.testkit.runner.TaskOutcome;
|
||||
import org.junit.jupiter.api.TestTemplate;
|
||||
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.gradle.junit.GradleCompatibilityExtension;
|
||||
import org.springframework.boot.gradle.testkit.GradleBuild;
|
||||
import org.springframework.boot.testsupport.testcontainers.DisabledIfDockerUnavailable;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Integration tests for {@link BootBuildImage}.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
@ExtendWith(GradleCompatibilityExtension.class)
|
||||
@DisabledIfDockerUnavailable
|
||||
class BootBuildImageIntegrationTests {
|
||||
|
||||
GradleBuild gradleBuild;
|
||||
|
||||
@TestTemplate
|
||||
void bootBuildImageBuildsImage() throws IOException {
|
||||
writeMainClass();
|
||||
BuildResult result = this.gradleBuild.build("bootBuildImage");
|
||||
assertThat(result.task(":bootBuildImage").getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
|
||||
ImageReference imageReference = ImageReference.of(ImageName.of(this.gradleBuild.getProjectDir().getName()));
|
||||
try (GenericContainer<?> container = new GenericContainer<>(imageReference.toString())) {
|
||||
container.waitingFor(Wait.forLogMessage("Launched\\n", 1)).start();
|
||||
}
|
||||
finally {
|
||||
new DockerApi().image().remove(imageReference, false);
|
||||
}
|
||||
}
|
||||
|
||||
private void writeMainClass() {
|
||||
File examplePackage = new File(this.gradleBuild.getProjectDir(), "src/main/java/example");
|
||||
examplePackage.mkdirs();
|
||||
File main = new File(examplePackage, "Main.java");
|
||||
try (PrintWriter writer = new PrintWriter(new FileWriter(main))) {
|
||||
writer.println("package example;");
|
||||
writer.println();
|
||||
writer.println("import java.io.IOException;");
|
||||
writer.println();
|
||||
writer.println("public class Main {");
|
||||
writer.println();
|
||||
writer.println(" public static void main(String[] args) throws Exception {");
|
||||
writer.println(" System.out.println(\"Launched\");");
|
||||
writer.println(" synchronized(args) {");
|
||||
writer.println(" args.wait(); // Prevent exit");
|
||||
writer.println(" }");
|
||||
writer.println(" }");
|
||||
writer.println();
|
||||
writer.println("}");
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
/*
|
||||
* Copyright 2012-2020 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.gradle.tasks.bundling;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.testfixtures.ProjectBuilder;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
|
||||
import org.springframework.boot.buildpack.platform.build.BuildRequest;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link BootBuildImage}.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
class BootBuildImageTests {
|
||||
|
||||
@TempDir
|
||||
File temp;
|
||||
|
||||
Project project;
|
||||
|
||||
private final BootBuildImage buildImage;
|
||||
|
||||
BootBuildImageTests() {
|
||||
File projectDir = new File(this.temp, "project");
|
||||
projectDir.mkdirs();
|
||||
this.project = ProjectBuilder.builder().withProjectDir(projectDir).withName("build-image-test").build();
|
||||
this.project.setDescription("Test project for BootBuildImage");
|
||||
this.buildImage = this.project.getTasks().create("buildImage", BootBuildImage.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenProjectVersionIsUnspecifiedThenItIsIgnoredWhenDerivingImageName() {
|
||||
BuildRequest request = this.buildImage.createRequest();
|
||||
assertThat(request.getName().getDomain()).isEqualTo("docker.io");
|
||||
assertThat(request.getName().getName()).isEqualTo("library/build-image-test");
|
||||
assertThat(request.getName().getTag()).isEqualTo("latest");
|
||||
assertThat(request.getName().getDigest()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenProjectVersionIsSpecifiedThenItIsUsedInTagOfImageName() {
|
||||
this.project.setVersion("1.2.3");
|
||||
BuildRequest request = this.buildImage.createRequest();
|
||||
assertThat(request.getName().getDomain()).isEqualTo("docker.io");
|
||||
assertThat(request.getName().getName()).isEqualTo("library/build-image-test");
|
||||
assertThat(request.getName().getTag()).isEqualTo("1.2.3");
|
||||
assertThat(request.getName().getDigest()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenImageNameIsSpecifiedThenItIsUsedInRequest() {
|
||||
this.project.setVersion("1.2.3");
|
||||
this.buildImage.setImageName("example.com/test/build-image:1.0");
|
||||
BuildRequest request = this.buildImage.createRequest();
|
||||
assertThat(request.getName().getDomain()).isEqualTo("example.com");
|
||||
assertThat(request.getName().getName()).isEqualTo("test/build-image");
|
||||
assertThat(request.getName().getTag()).isEqualTo("1.0");
|
||||
assertThat(request.getName().getDigest()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenIndividualEntriesAreAddedToTheEnvironmentThenTheyAreIncludedInTheRequest() {
|
||||
this.buildImage.environment("ALPHA", "a");
|
||||
this.buildImage.environment("BRAVO", "b");
|
||||
assertThat(this.buildImage.createRequest().getEnv()).containsEntry("ALPHA", "a").containsEntry("BRAVO", "b")
|
||||
.hasSize(2);
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenEntriesAreAddedToTheEnvironmentThenTheyAreIncludedInTheRequest() {
|
||||
Map<String, String> environment = new HashMap<String, String>();
|
||||
environment.put("ALPHA", "a");
|
||||
environment.put("BRAVO", "b");
|
||||
this.buildImage.environment(environment);
|
||||
assertThat(this.buildImage.createRequest().getEnv()).containsEntry("ALPHA", "a").containsEntry("BRAVO", "b")
|
||||
.hasSize(2);
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenTheEnvironmentIsSetItIsIncludedInTheRequest() {
|
||||
Map<String, String> environment = new HashMap<String, String>();
|
||||
environment.put("ALPHA", "a");
|
||||
environment.put("BRAVO", "b");
|
||||
this.buildImage.setEnvironment(environment);
|
||||
assertThat(this.buildImage.createRequest().getEnv()).containsEntry("ALPHA", "a").containsEntry("BRAVO", "b")
|
||||
.hasSize(2);
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenTheEnvironmentIsSetItReplacesAnyExistingEntriesAndIsIncludedInTheRequest() {
|
||||
Map<String, String> environment = new HashMap<String, String>();
|
||||
environment.put("ALPHA", "a");
|
||||
environment.put("BRAVO", "b");
|
||||
this.buildImage.environment("C", "Charlie");
|
||||
this.buildImage.setEnvironment(environment);
|
||||
assertThat(this.buildImage.createRequest().getEnv()).containsEntry("ALPHA", "a").containsEntry("BRAVO", "b")
|
||||
.hasSize(2);
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenUsingDefaultConfigurationThenRequestHasVerboseLoggingDisabled() {
|
||||
assertThat(this.buildImage.createRequest().isVerboseLogging()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenVerboseLoggingIsEnabledThenRequestHasVerboseLoggingEnabled() {
|
||||
this.buildImage.setVerboseLogging(true);
|
||||
assertThat(this.buildImage.createRequest().isVerboseLogging()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenUsingDefaultConfigurationThenRequestHasCleanCacheDisabled() {
|
||||
assertThat(this.buildImage.createRequest().isCleanCache()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenCleanCacheIsEnabledThenRequestHasCleanCacheEnabled() {
|
||||
this.buildImage.setCleanCache(true);
|
||||
assertThat(this.buildImage.createRequest().isCleanCache()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenNoBuilderIsConfiguredThenRequestHasDefaultBuilder() {
|
||||
assertThat(this.buildImage.createRequest().getBuilder().getName()).isEqualTo("cloudfoundry/cnb");
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenBuilderIsConfiguredThenRequestUsesSpecifiedBuilder() {
|
||||
this.buildImage.setBuilder("example.com/test/builder:1.2");
|
||||
assertThat(this.buildImage.createRequest().getBuilder().getName()).isEqualTo("test/builder");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -27,9 +27,16 @@ import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.jar.JarFile;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonView;
|
||||
import com.fasterxml.jackson.core.Versioned;
|
||||
import com.fasterxml.jackson.databind.Module;
|
||||
import com.fasterxml.jackson.module.paramnames.ParameterNamesModule;
|
||||
import com.sun.jna.Platform;
|
||||
import io.spring.gradle.dependencymanagement.DependencyManagementPlugin;
|
||||
import io.spring.gradle.dependencymanagement.dsl.DependencyManagementExtension;
|
||||
import org.apache.commons.compress.archivers.ArchiveEntry;
|
||||
import org.apache.http.HttpRequest;
|
||||
import org.apache.http.conn.HttpClientConnectionManager;
|
||||
import org.gradle.testkit.runner.BuildResult;
|
||||
import org.gradle.testkit.runner.GradleRunner;
|
||||
import org.gradle.util.GradleVersion;
|
||||
@@ -41,6 +48,7 @@ import org.jetbrains.kotlin.gradle.plugin.KotlinGradleSubplugin;
|
||||
import org.jetbrains.kotlin.gradle.plugin.KotlinPlugin;
|
||||
|
||||
import org.springframework.asm.ClassVisitor;
|
||||
import org.springframework.boot.buildpack.platform.build.BuildRequest;
|
||||
import org.springframework.boot.loader.tools.LaunchScript;
|
||||
import org.springframework.util.FileCopyUtils;
|
||||
import org.springframework.util.FileSystemUtils;
|
||||
@@ -94,7 +102,12 @@ public class GradleBuild {
|
||||
new File(pathOfJarContaining(KotlinPlugin.class)), new File(pathOfJarContaining(KotlinProject.class)),
|
||||
new File(pathOfJarContaining(KotlinCompilerClient.class)),
|
||||
new File(pathOfJarContaining(KotlinGradleSubplugin.class)),
|
||||
new File(pathOfJarContaining(ArchiveEntry.class)));
|
||||
new File(pathOfJarContaining(ArchiveEntry.class)), new File(pathOfJarContaining(BuildRequest.class)),
|
||||
new File(pathOfJarContaining(HttpClientConnectionManager.class)),
|
||||
new File(pathOfJarContaining(HttpRequest.class)), new File(pathOfJarContaining(Module.class)),
|
||||
new File(pathOfJarContaining(Versioned.class)),
|
||||
new File(pathOfJarContaining(ParameterNamesModule.class)),
|
||||
new File(pathOfJarContaining(JsonView.class)), new File(pathOfJarContaining(Platform.class)));
|
||||
}
|
||||
|
||||
private String pathOfJarContaining(Class<?> type) {
|
||||
|
||||
Reference in New Issue
Block a user