Add buildpack option for image building

This commit adds configuration to the Maven and Gradle plugins to
allow a list of buildpacks to be provided to the image building
goal and task.

Fixes gh-21722
This commit is contained in:
Scott Frederick
2021-02-18 17:28:25 -06:00
parent d8fe9de682
commit f54f784f80
74 changed files with 3895 additions and 211 deletions

View File

@@ -130,6 +130,18 @@ Acceptable values are `ALWAYS`, `NEVER`, and `IF_NOT_PRESENT`.
| Environment variables that should be passed to the builder.
|
| `buildpacks`
|
a|Buildpacks that the builder should use when building the image.
Only the specified buildpacks will be used, overriding the default buildpacks included in the builder.
Buildpack references must be in one of the following forms:
* Buildpack in the builder - [urn:cnb:builder:]<buildpack id>[@<version>]
* Buildpack in a directory on the file system - [file://]<path>
* Buildpack in a gzipped tar (.tgz) file on the file system - [file://]<path>/<file name>
* Buildpack in an OCI image - [docker://]<host>/<repo>[:<tag>][@<digest>]
| None, indicating the builder should use the buildpacks included in it.
| `cleanCache`
| `--cleanCache`
| Whether to clean the cache before building.
@@ -249,6 +261,58 @@ The image name can be specified on the command line as well, as shown in this ex
$ gradle bootBuildImage --imageName=example.com/library/my-app:v1
----
[[build-image-example-buildpacks]]
==== Buildpacks
By default, the builder will use buildpacks included in the builder image and apply them in a pre-defined order.
An alternative set of buildpacks can be provided to apply buildpacks that are not included in the builder, or to change the order of included buildpacks.
When one or more buildpacks are provided, only the specified buildpacks will be applied.
The following example instructs the builder to use a custom buildpack packaged in a `.tgz` file, followed by a buildpack included in the builder.
[source,groovy,indent=0,subs="verbatim,attributes",role="primary"]
.Groovy
----
include::../gradle/packaging/boot-build-image-buildpacks.gradle[tags=buildpacks]
----
[source,kotlin,indent=0,subs="verbatim,attributes",role="secondary"]
.Kotlin
----
include::../gradle/packaging/boot-build-image-buildpacks.gradle.kts[tags=buildpacks]
----
Buildpacks can be specified in any of the forms shown below.
A buildpack located in a CNB Builder (version may be omitted if there is only one buildpack in the builder matching the `buildpack-id`):
* `urn:cnb:builder:buildpack-id`
* `urn:cnb:builder:buildpack-id@0.0.1`
* `buildpack-id`
* `buildpack-id@0.0.1`
A path to a directory containing buildpack content (not supported on Windows):
* `\file:///path/to/buildpack/`
* `/path/to/buildpack/`BootBuildImageIntegrationTests
A path to a gzipped tar file containing buildpack content:
* `\file:///path/to/buildpack.tgz`
* `/path/to/buildpack.tgz`
An OCI image containing a https://buildpacks.io/docs/buildpack-author-guide/package-a-buildpack/[packaged buildpack]:
* `docker://example/buildpack`
* `docker:///example/buildpack:latest`
* `docker:///example/buildpack@sha256:45b23dee08...`
* `example/buildpack`
* `example/buildpack:latest`
* `example/buildpack@sha256:45b23dee08...`
[[build-image-example-publish]]
==== Image Publishing
The generated image can be published to a Docker registry by enabling a `publish` option and configuring authentication for the registry using `docker.publishRegistry` properties.
@@ -272,6 +336,8 @@ The publish option can be specified on the command line as well, as shown in thi
$ gradle bootBuildImage --imageName=docker.example.com/library/my-app:v1 --publishImage
----
[[build-image-example-docker]]
==== Docker Configuration
If you need the plugin to communicate with the Docker daemon using a remote connection instead of the default local connection, the connection details can be provided using `docker` properties as shown in the following example:

View File

@@ -0,0 +1,16 @@
plugins {
id 'java'
id 'org.springframework.boot' version '{gradle-project-version}'
}
// tag::buildpacks[]
bootBuildImage {
buildpacks = ["file:///path/to/example-buildpack.tgz", "urn:cnb:builder:paketo-buildpacks/java"]
}
// end::buildpacks[]
task bootBuildImageBuildpacks {
doFirst {
bootBuildImage.buildpacks.each { reference -> println "$reference" }
}
}

View File

@@ -0,0 +1,20 @@
import org.springframework.boot.gradle.tasks.bundling.BootBuildImage
plugins {
java
id("org.springframework.boot") version "{gradle-project-version}"
}
// tag::buildpacks[]
tasks.getByName<BootBuildImage>("bootBuildImage") {
buildpacks = listOf("file:///path/to/example-buildpack.tgz", "urn:cnb:builder:paketo-buildpacks/java")
}
// end::buildpacks[]
tasks.register("bootBuildImageBuildpacks") {
doFirst {
for((reference) in tasks.getByName<BootBuildImage>("bootBuildImage").buildpacks) {
print(reference)
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2020 the original author or authors.
* Copyright 2012-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,7 +18,9 @@ package org.springframework.boot.gradle.tasks.bundling;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import groovy.lang.Closure;
import org.gradle.api.Action;
@@ -28,6 +30,7 @@ import org.gradle.api.JavaVersion;
import org.gradle.api.Project;
import org.gradle.api.Task;
import org.gradle.api.file.RegularFileProperty;
import org.gradle.api.provider.ListProperty;
import org.gradle.api.provider.Property;
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.Nested;
@@ -38,6 +41,7 @@ import org.gradle.util.ConfigureUtil;
import org.springframework.boot.buildpack.platform.build.BuildRequest;
import org.springframework.boot.buildpack.platform.build.Builder;
import org.springframework.boot.buildpack.platform.build.BuildpackReference;
import org.springframework.boot.buildpack.platform.build.Creator;
import org.springframework.boot.buildpack.platform.build.PullPolicy;
import org.springframework.boot.buildpack.platform.docker.transport.DockerEngineException;
@@ -83,6 +87,8 @@ public class BootBuildImage extends DefaultTask {
private boolean publish;
private ListProperty<String> buildpacks;
private DockerSpec docker = new DockerSpec();
public BootBuildImage() {
@@ -92,6 +98,7 @@ public class BootBuildImage extends DefaultTask {
this.projectVersion = getProject().getObjects().property(String.class);
Project project = getProject();
this.projectVersion.set(getProject().provider(() -> project.getVersion().toString()));
this.buildpacks = getProject().getObjects().listProperty(String.class);
}
/**
@@ -283,6 +290,40 @@ public class BootBuildImage extends DefaultTask {
this.publish = publish;
}
/**
* Returns the buildpacks that will be used when building the image.
* @return the buildpacks
*/
@Input
@Optional
public List<String> getBuildpacks() {
return this.buildpacks.getOrNull();
}
/**
* Sets the buildpacks that will be used when building the image.
* @param buildpacks the buildpacks
*/
public void setBuildpacks(List<String> buildpacks) {
this.buildpacks.set(buildpacks);
}
/**
* Add an entry to the buildpacks that will be used when building the image.
* @param buildpack the buildpack reference
*/
public void buildpack(String buildpack) {
this.buildpacks.add(buildpack);
}
/**
* Adds entries to the environment that will be used when building the image.
* @param buildpacks the buildpack references
*/
public void buildpacks(List<String> buildpacks) {
this.buildpacks.addAll(buildpacks);
}
/**
* Returns the Docker configuration the builder will use.
* @return docker configuration.
@@ -316,8 +357,8 @@ public class BootBuildImage extends DefaultTask {
if (!this.jar.isPresent()) {
throw new GradleException("Executable jar file required for building image");
}
Builder builder = new Builder(this.docker.asDockerConfiguration());
BuildRequest request = createRequest();
Builder builder = new Builder(this.docker.asDockerConfiguration());
builder.build(request);
}
@@ -346,6 +387,7 @@ public class BootBuildImage extends DefaultTask {
request = request.withVerboseLogging(this.verboseLogging);
request = customizePullPolicy(request);
request = customizePublish(request);
request = customizeBuildpacks(request);
return request;
}
@@ -398,6 +440,14 @@ public class BootBuildImage extends DefaultTask {
return request;
}
private BuildRequest customizeBuildpacks(BuildRequest request) {
List<String> buildpacks = this.buildpacks.getOrNull();
if (buildpacks != null && !buildpacks.isEmpty()) {
return request.withBuildpacks(buildpacks.stream().map(BuildpackReference::of).collect(Collectors.toList()));
}
return request;
}
private String translateTargetJavaVersion() {
return this.targetJavaVersion.get().getMajorVersion() + ".*";
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2020 the original author or authors.
* Copyright 2012-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -23,11 +23,21 @@ import java.io.PrintWriter;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.attribute.FileAttribute;
import java.nio.file.attribute.PosixFilePermission;
import java.nio.file.attribute.PosixFilePermissions;
import java.util.Random;
import java.util.Set;
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream;
import org.apache.commons.compress.compressors.gzip.GzipCompressorOutputStream;
import org.apache.commons.compress.utils.IOUtils;
import org.gradle.testkit.runner.BuildResult;
import org.gradle.testkit.runner.TaskOutcome;
import org.junit.jupiter.api.TestTemplate;
import org.junit.jupiter.api.condition.DisabledOnOs;
import org.junit.jupiter.api.condition.OS;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.wait.strategy.Wait;
@@ -145,7 +155,72 @@ class BootBuildImageIntegrationTests {
}
@TestTemplate
void failsWithLaunchScript() {
void buildsImageWithBuildpackFromBuilder() throws IOException {
writeMainClass();
writeLongNameResource();
BuildResult result = this.gradleBuild.build("bootBuildImage", "--pullPolicy=IF_NOT_PRESENT");
String projectName = this.gradleBuild.getProjectDir().getName();
assertThat(result.task(":bootBuildImage").getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
assertThat(result.getOutput()).contains("docker.io/library/" + projectName);
ImageReference imageReference = ImageReference.of(ImageName.of(projectName));
try (GenericContainer<?> container = new GenericContainer<>(imageReference.toString())) {
container.waitingFor(Wait.forLogMessage("Launched\\n", 1)).start();
}
finally {
new DockerApi().image().remove(imageReference, false);
}
}
@TestTemplate
@DisabledOnOs(OS.WINDOWS)
void buildsImageWithBuildpackFromDirectory() throws IOException {
writeMainClass();
writeLongNameResource();
writeBuildpackContent();
BuildResult result = this.gradleBuild.build("bootBuildImage", "--pullPolicy=IF_NOT_PRESENT");
String projectName = this.gradleBuild.getProjectDir().getName();
ImageReference imageReference = ImageReference.of(ImageName.of(projectName));
assertThat(result.task(":bootBuildImage").getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
assertThat(result.getOutput()).contains("docker.io/library/" + projectName);
assertThat(result.getOutput()).contains("---> Hello World buildpack");
new DockerApi().image().remove(imageReference, false);
}
@TestTemplate
@DisabledOnOs(OS.WINDOWS)
void buildsImageWithBuildpackFromTarGzip() throws IOException {
writeMainClass();
writeLongNameResource();
writeBuildpackContent();
tarGzipBuildpackContent();
BuildResult result = this.gradleBuild.build("bootBuildImage", "--pullPolicy=IF_NOT_PRESENT");
String projectName = this.gradleBuild.getProjectDir().getName();
ImageReference imageReference = ImageReference.of(ImageName.of(projectName));
assertThat(result.task(":bootBuildImage").getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
assertThat(result.getOutput()).contains("docker.io/library/" + projectName);
assertThat(result.getOutput()).contains("---> Hello World buildpack");
new DockerApi().image().remove(imageReference, false);
}
@TestTemplate
void buildsImageWithBuildpackFromImage() throws IOException {
writeMainClass();
writeLongNameResource();
BuildResult result = this.gradleBuild.build("bootBuildImage", "--pullPolicy=IF_NOT_PRESENT");
String projectName = this.gradleBuild.getProjectDir().getName();
assertThat(result.task(":bootBuildImage").getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
assertThat(result.getOutput()).contains("docker.io/library/" + projectName);
ImageReference imageReference = ImageReference.of(ImageName.of(projectName));
try (GenericContainer<?> container = new GenericContainer<>(imageReference.toString())) {
container.waitingFor(Wait.forLogMessage("Launched\\n", 1)).start();
}
finally {
new DockerApi().image().remove(imageReference, false);
}
}
@TestTemplate
void failsWithLaunchScript() throws IOException {
writeMainClass();
writeLongNameResource();
BuildResult result = this.gradleBuild.buildAndFail("bootBuildImage");
@@ -154,7 +229,7 @@ class BootBuildImageIntegrationTests {
}
@TestTemplate
void failsWithBuilderError() {
void failsWithBuilderError() throws IOException {
writeMainClass();
writeLongNameResource();
BuildResult result = this.gradleBuild.buildAndFail("bootBuildImage", "--pullPolicy=IF_NOT_PRESENT");
@@ -163,7 +238,7 @@ class BootBuildImageIntegrationTests {
}
@TestTemplate
void failsWithInvalidImageName() {
void failsWithInvalidImageName() throws IOException {
writeMainClass();
writeLongNameResource();
BuildResult result = this.gradleBuild.buildAndFail("bootBuildImage", "--imageName=example/Invalid-Image-Name");
@@ -173,7 +248,7 @@ class BootBuildImageIntegrationTests {
}
@TestTemplate
void failsWithPublishMissingPublishRegistry() {
void failsWithPublishMissingPublishRegistry() throws IOException {
writeMainClass();
writeLongNameResource();
BuildResult result = this.gradleBuild.buildAndFail("bootBuildImage", "--publishImage");
@@ -182,7 +257,7 @@ class BootBuildImageIntegrationTests {
}
@TestTemplate
void failsWithWarPackaging() {
void failsWithWarPackaging() throws IOException {
writeMainClass();
writeLongNameResource();
BuildResult result = this.gradleBuild.buildAndFail("bootBuildImage", "-PapplyWarPlugin");
@@ -190,6 +265,15 @@ class BootBuildImageIntegrationTests {
assertThat(result.getOutput()).contains("Executable jar file required for building image");
}
@TestTemplate
void failsWithBuildpackNotInBuilder() throws IOException {
writeMainClass();
writeLongNameResource();
BuildResult result = this.gradleBuild.buildAndFail("bootBuildImage", "--pullPolicy=IF_NOT_PRESENT");
assertThat(result.task(":bootBuildImage").getOutcome()).isEqualTo(TaskOutcome.FAILED);
assertThat(result.getOutput()).contains("'urn:cnb:builder:example/does-not-exist:0.0.1' not found in builder");
}
@TestTemplate
void buildsImageWithWarPackagingAndJarConfiguration() throws IOException {
writeMainClass();
@@ -210,7 +294,7 @@ class BootBuildImageIntegrationTests {
}
}
private void writeMainClass() {
private void writeMainClass() throws IOException {
File examplePackage = new File(this.gradleBuild.getProjectDir(), "src/main/java/example");
examplePackage.mkdirs();
File main = new File(examplePackage, "Main.java");
@@ -230,23 +314,70 @@ class BootBuildImageIntegrationTests {
writer.println();
writer.println("}");
}
catch (IOException ex) {
throw new RuntimeException(ex);
}
private void writeLongNameResource() throws IOException {
StringBuilder name = new StringBuilder();
new Random().ints('a', 'z' + 1).limit(128).forEach((i) -> name.append((char) i));
Path path = this.gradleBuild.getProjectDir().toPath()
.resolve(Paths.get("src", "main", "resources", name.toString()));
Files.createDirectories(path.getParent());
Files.createFile(path);
}
private void writeBuildpackContent() throws IOException {
File buildpackDir = new File(this.gradleBuild.getProjectDir(), "buildpack/hello-world");
buildpackDir.mkdirs();
File descriptor = new File(buildpackDir, "buildpack.toml");
try (PrintWriter writer = new PrintWriter(new FileWriter(descriptor))) {
writer.println("api = \"0.2\"");
writer.println("[buildpack]");
writer.println("id = \"example/hello-world\"");
writer.println("version = \"0.0.1\"");
writer.println("name = \"Hello World Buildpack\"");
writer.println("homepage = \"https://github.com/buildpacks/samples/tree/main/buildpacks/hello-world\"");
writer.println("[[stacks]]\n");
writer.println("id = \"io.buildpacks.stacks.bionic\"");
}
File binDir = new File(buildpackDir, "bin");
binDir.mkdirs();
FileAttribute<Set<PosixFilePermission>> attribute = PosixFilePermissions
.asFileAttribute(PosixFilePermissions.fromString("rwxrwxrwx"));
File detect = Files.createFile(Paths.get(binDir.getAbsolutePath(), "detect"), attribute).toFile();
try (PrintWriter writer = new PrintWriter(new FileWriter(detect))) {
writer.println("#!/usr/bin/env bash");
writer.println("set -eo pipefail");
writer.println("exit 0");
}
File build = Files.createFile(Paths.get(binDir.getAbsolutePath(), "build"), attribute).toFile();
try (PrintWriter writer = new PrintWriter(new FileWriter(build))) {
writer.println("#!/usr/bin/env bash");
writer.println("set -eo pipefail");
writer.println("echo \"---> Hello World buildpack\"");
writer.println("echo \"---> done\"");
writer.println("exit 0");
}
}
private void writeLongNameResource() {
StringBuilder name = new StringBuilder();
new Random().ints('a', 'z' + 1).limit(128).forEach((i) -> name.append((char) i));
try {
Path path = this.gradleBuild.getProjectDir().toPath()
.resolve(Paths.get("src", "main", "resources", name.toString()));
Files.createDirectories(path.getParent());
Files.createFile(path);
}
catch (IOException ex) {
throw new RuntimeException(ex);
private void tarGzipBuildpackContent() throws IOException {
Path tarGzipPath = Paths.get(this.gradleBuild.getProjectDir().getAbsolutePath(), "hello-world.tgz");
try (TarArchiveOutputStream tar = new TarArchiveOutputStream(
new GzipCompressorOutputStream(Files.newOutputStream(Files.createFile(tarGzipPath))))) {
writeFileToTar(tar, new File(this.gradleBuild.getProjectDir(), "buildpack/hello-world/buildpack.toml"),
"buildpack.toml", 0644);
writeFileToTar(tar, new File(this.gradleBuild.getProjectDir(), "buildpack/hello-world/bin/detect"),
"bin/detect", 0777);
writeFileToTar(tar, new File(this.gradleBuild.getProjectDir(), "buildpack/hello-world/bin/build"),
"bin/build", 0777);
}
}
private void writeFileToTar(TarArchiveOutputStream tar, File file, String name, int mode) throws IOException {
TarArchiveEntry entry = new TarArchiveEntry(file, name);
entry.setMode(mode);
tar.putArchiveEntry(entry);
IOUtils.copy(Files.newInputStream(file.toPath()), tar);
tar.closeArchiveEntry();
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2020 the original author or authors.
* Copyright 2012-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,6 +17,7 @@
package org.springframework.boot.gradle.tasks.bundling;
import java.io.File;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
@@ -28,6 +29,7 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.springframework.boot.buildpack.platform.build.BuildRequest;
import org.springframework.boot.buildpack.platform.build.BuildpackReference;
import org.springframework.boot.buildpack.platform.build.PullPolicy;
import static org.assertj.core.api.Assertions.assertThat;
@@ -221,4 +223,31 @@ class BootBuildImageTests {
assertThat(this.buildImage.createRequest().getPullPolicy()).isEqualTo(PullPolicy.NEVER);
}
@Test
void whenNoBuildpacksAreConfiguredThenRequestUsesDefaultBuildpacks() {
assertThat(this.buildImage.createRequest().getBuildpacks()).isEmpty();
}
@Test
void whenBuildpacksAreConfiguredThenRequestHasBuildpacks() {
this.buildImage.setBuildpacks(Arrays.asList("example/buildpack1", "example/buildpack2"));
assertThat(this.buildImage.createRequest().getBuildpacks()).containsExactly(
BuildpackReference.of("example/buildpack1"), BuildpackReference.of("example/buildpack2"));
}
@Test
void whenEntriesAreAddedToBuildpacksThenRequestHasBuildpacks() {
this.buildImage.buildpacks(Arrays.asList("example/buildpack1", "example/buildpack2"));
assertThat(this.buildImage.createRequest().getBuildpacks()).containsExactly(
BuildpackReference.of("example/buildpack1"), BuildpackReference.of("example/buildpack2"));
}
@Test
void whenIndividualEntriesAreAddedToBuildpacksThenRequestHasBuildpacks() {
this.buildImage.buildpack("example/buildpack1");
this.buildImage.buildpack("example/buildpack2");
assertThat(this.buildImage.createRequest().getBuildpacks()).containsExactly(
BuildpackReference.of("example/buildpack1"), BuildpackReference.of("example/buildpack2"));
}
}

View File

@@ -37,6 +37,7 @@ 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.antlr.v4.runtime.Lexer;
import org.apache.commons.compress.archivers.ArchiveEntry;
import org.apache.http.HttpRequest;
import org.apache.http.conn.HttpClientConnectionManager;
@@ -49,6 +50,7 @@ import org.jetbrains.kotlin.daemon.client.KotlinCompilerClient;
import org.jetbrains.kotlin.gradle.model.KotlinProject;
import org.jetbrains.kotlin.gradle.plugin.KotlinGradleSubplugin;
import org.jetbrains.kotlin.gradle.plugin.KotlinPlugin;
import org.tomlj.Toml;
import org.springframework.asm.ClassVisitor;
import org.springframework.boot.buildpack.platform.build.BuildRequest;
@@ -116,7 +118,8 @@ public class GradleBuild {
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)));
new File(pathOfJarContaining(JsonView.class)), new File(pathOfJarContaining(Platform.class)),
new File(pathOfJarContaining(Toml.class)), new File(pathOfJarContaining(Lexer.class)));
}
private String pathOfJarContaining(Class<?> type) {

View File

@@ -0,0 +1,11 @@
plugins {
id 'java'
id 'org.springframework.boot' version '{version}'
}
sourceCompatibility = '1.8'
targetCompatibility = '1.8'
bootBuildImage {
buildpacks = [ "paketo-buildpacks/java" ]
}

View File

@@ -0,0 +1,11 @@
plugins {
id 'java'
id 'org.springframework.boot' version '{version}'
}
sourceCompatibility = '1.8'
targetCompatibility = '1.8'
bootBuildImage {
buildpacks = [ "file://${projectDir}/buildpack/hello-world" ]
}

View File

@@ -0,0 +1,11 @@
plugins {
id 'java'
id 'org.springframework.boot' version '{version}'
}
sourceCompatibility = '1.8'
targetCompatibility = '1.8'
bootBuildImage {
buildpacks = [ "gcr.io/paketo-buildpacks/java:latest" ]
}

View File

@@ -0,0 +1,11 @@
plugins {
id 'java'
id 'org.springframework.boot' version '{version}'
}
sourceCompatibility = '1.8'
targetCompatibility = '1.8'
bootBuildImage {
buildpacks = [ "file://${projectDir}/hello-world.tgz" ]
}

View File

@@ -0,0 +1,11 @@
plugins {
id 'java'
id 'org.springframework.boot' version '{version}'
}
sourceCompatibility = '1.8'
targetCompatibility = '1.8'
bootBuildImage {
buildpacks = [ "urn:cnb:builder:example/does-not-exist:0.0.1" ]
}