snowdrop / java-buildpack-client

A java buildpack (https://buildpacks.io/) platform implementation
Apache License 2.0
14 stars 7 forks source link
buildpack buildpacks hacktoberfest java library paketo

Java buildpack Client

Build Maven Central

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.

Overview

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.

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

How To:

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.

  1. 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> 
  2. Instantiate a BuildConfig

    BuildConfig bc = BuildConfig.builder();
  3. Define the content to be built..

    bc = bc.addNewFileContentApplication(new File("/path/to/the/project/to/build));
  4. Configure the name/tags for the image to create

    bc = bc.withOutputImage(new ImageReference("myorg/myimage:mytag"));
  5. Invoke the build

    bc.build();
  6. 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();

Logging

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();

Inline Logger configuration

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();

Error Handling

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.

Using the buildpack client

Maven exec:java

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

Jbang

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.

FAQ:

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!