Prototype of a simple buildpack (https://buildpacks.io/) client for java.
This project represents a simple implementation of the buildpack platform spec, and can be used to build projects using specified buildpacks.
This project implements up to version 0.10 of the buildpack platform spec, and should work with builders requesting from 0.4 through to 0.10. 0.12 is available but experimental.
A fluent interface is provided to allow creation & configuration of the build to be performed.
A very simple build can be performed with as little as.
int exitCode = BuildConfig.builder()
.withOutputImage(new ImageReference("test/testimage:latest"))
.addNewFileContentApplication(new File("/home/user/java-project"))
.build()
.getExitCode();
This will use the default builder image from (https://paketo.io) to handle the build
of the project in the /home/user/java-project
folder. The resulting image will
be stored in the local docker daemon as test/testimage:latest
.
The BuildpackConfig
offers other configuration methods to customise behavior.
A variety of methods are supported for adding content to be build, content is combined in the order passed, allowing for sparse source directories, or multiple project dirs to be combined.
ContainerEntry
interface, for custom integration.Build/RunImages will be pulled as required.
The builder will use docker via the DOCKER_HOST
env var, if configured, or via the platform appropriate docker socket if not.
Alternatively, dockerHost can be directly configured on the builder itself. If the docker host starts with unix://
the path to the
docker socket is extracted and used during the build phase. If unset, this defaults to /var/run/docker/sock
Want to try out this project? The packages/api are not fixed in stone yet, so be aware! But here are the basic steps to get you up and running.
Add this project as a dependency.. (use the latest version instead of XXX)
<dependency>
<groupId>dev.snowdrop</groupId>
<artifactId>buildpack-client</artifactId>
<version>0.0.12</version>
</dependency>
Instantiate a BuildConfig
BuildConfig bc = BuildConfig.builder();
Define the content to be built..
bc = bc.addNewFileContentApplication(new File("/path/to/the/project/to/build));
Configure the name/tags for the image to create
bc = bc.withOutputImage(new ImageReference("myorg/myimage:mytag"));
Invoke the build
bc.build();
Retrieve the build exit code
bc.getExitCode();
Or combine all the above steps into a single callchain.
int exitCode = BuildConfig.builder()
.withOutputImage(new ImageReference("test/testimage:latest"))
.addNewFileContentApplication(new File("/home/user/java-project"))
.build()
.getExitCode();
There are many more ways to customize & configure the BuildConfig, take a look at the interface to see everything thats currently possible.
A demo project has been created to allow easy exploration of uses of BuildConfig
here :-)
Most likely if you are using this to integrate to existing tooling, you will want to supply a custom LogReader to receive the messages output by the Build Containers during the build. You may also want to associate cache names to a project, to enable faster rebuilds for a given project. Note that if you wish caches to survive beyond a build, you should set deleteCacheAfterBuild
to false
for each cache. Eg.
int exitCode = BuildConfig.builder()
.withNewBuildCacheConfig()
.withCacheVolumeName("my-project-specific-cache-name")
.withDeleteCacheAfterBuild(true)
.and()
.withOutputImage(new ImageReference("test/testimage:latest"))
.addNewFileContentApplication(new File("/home/user/java-project"))
.build()
.getExitCode();
Output from the Buildpack execution is available via the dev.snowdrop.buildpack.Logger
interface, which can be optionally be passed using the builder.
At the moment two kinds of logger are supported:
Both can be configured using the builder:
int exitCode = BuildConfig.builder()
.withNewLogConfig()
.withLogger(new SystemLogger())
.withLogLevel("debug")
.and()
.withOutputImage(new ImageReference("test/testimage:latest"))
.addNewFileContentApplication(new File("/home/user/java-project"))
.build()
.getExitCode();
or
int exitCode = BuildConfig.builder()
.withNewLogConfig()
.withLogger(new Slf4jLogger())
.withLogLevel("debug")
.and()
.withOutputImage(new ImageReference("test/testimage:latest"))
.addNewFileContentApplication(new File("/home/user/java-project"))
.build()
.getExitCode();
The builder DSL supports inlining Logger
configuration:
int exitCode = BuildConfig.builder()
.withNewLogConfig()
.withNewSystemLogger(false)
.withLogLevel("debug")
.and()
.withOutputImage(new ImageReference("test/testimage:latest"))
.addNewFileContentApplication(new File("/home/user/java-project"))
.build()
.getExitCode();
The above statement configures system logger with disabled ansi colors.
Similarly, with Slf4jLogger
one can inline the name of the logger:
int exitCode = BuildConfig.builder()
.withNewLogConfig()
.withNewSlf4jLogger(MyApp.class.getCanonicalName())
.withLogLevel("debug")
.and()
.withOutputImage(new ImageReference("test/testimage:latest"))
.addNewFileContentApplication(new File("/home/user/java-project"))
.build()
.getExitCode();
If the build fails for any reason, a BuildpackException
will be thrown, this is a RuntimeException, so does not need an explicit catch block. There are many ways in which a build can fail, from something environmental, like docker being unavailable, to build related issues, like the chosen builder image requiring a platformlevel not implemented by this library.
To play with the Java Buildpack client & DSL, use the following simple java project: samples/build-me
To use it, configure the following mandatory environment variables pointing to a project (example: hello-quarkus, hello-spring) to be built as a container image
export PROJECT_PATH=<JAVA_PROJECT>
export IMAGE_REF=<IMAGE_REF> // <IMAGE_NAME> without registry or <REGISTRY_SERVER>/<REGISTRY_ORG>/<IMAGE_NAME>
Important: To avoid the docker rate limit
problem, then set this CNB_
environment variable to let lifecycle
to get rid of the limit:
export CNB_REGISTRY_AUTH="'{"index.docker.io":"Basic <BASE64_OF_USERNAME:PASSWORD>"}'"
// Replace `<BASE64_OF_USERNAME:PASSWORD> text with
echo -n "username:password" | base64
If you plan to push your image to a registry, then set your registry credential using these variables:
export REGISTRY_USERNAME="<REGISTRY_USERNAME>"
export REGISTRY_PASSWORD="<REGISTRY_PASSWORD>"
export REGISTRY_SERVER="docker.io"
Execute this command in a terminal:
mvn compile exec:java
You can also pass the BP_
or CNB_
environment variables:
export BP_JVM_VERSION="21"
export BP_MAVEN_BUILT_ARTIFACT="target/quarkus-app/lib/ target/quarkus-app/*.jar target/quarkus-app/app/ target/quarkus-app/quarkus"
export CNB_LOG_LEVEL=debug
etc
The easiest way to invoke arbitrary java code, without much hassle is by using jbang.
So, you can drop the following file in your project: (swap XXX for latest release of buildpack-client)
///usr/bin/env jbang "$0" "$@" ; exit $?
//DEPS dev.snowdrop:buildpack-client:0.0.12}
import static java.lang.System.*;
import java.io.File;
import dev.snowdrop.buildpack.*;
public class pack {
public static void main(String... args) {
int exitCode = BuildConfig.builder()
.withOutputImage(new ImageReference("test/testimage:latest"))
.addNewFileContentApplication(new File("/home/user/java-project"))
.build()
.getExitCode();
System.exit(exitCode);
}
}
... and just run it using:
./pack.java
The samples use jbang too, but allow the version of the library to be set via an env var for use in our tests! samples.
Will this work with Podman?:
Yes, tested with Podman 4.7.0 on Fedora, rootless and rootful.
Does this work on Windows?:
Yes.. it's supposed to! Tested with Win10 + Docker on WSL2
Does this work on Linux?:
Yes.. Tested with Ubuntu & Fedora with Docker
Can I supply buildpacks/extensions to add to a builder like pack?:
The code is structured to allow for this, but the feature is not exposed via the builder api, as using additional buildpacks/extensions requires updating order.toml in the base builder, and that would require additional config to either override, or specify the behavior for manipulating the toml. If you are interested in this kind of functionality, raise an Issue so I can better understand the use case, or send a PR!