├── .github └── workflows │ ├── build-images.yml │ ├── dev-images.yml │ └── push-images.yml ├── .gitignore ├── LICENSE ├── README.md ├── jdock-variant-helper ├── pom.xml └── src │ └── main │ └── java │ └── io │ └── quarkus │ └── images │ └── config │ ├── Config.java │ ├── Tag.java │ └── Variant.java ├── jdock ├── pom.xml └── src │ ├── main │ ├── java │ │ └── io │ │ │ └── quarkus │ │ │ └── images │ │ │ ├── BuildContext.java │ │ │ ├── Buildable.java │ │ │ ├── Dockerfile.java │ │ │ ├── JDock.java │ │ │ ├── MultiArchImage.java │ │ │ ├── MultiStageDockerFile.java │ │ │ ├── artifacts │ │ │ ├── Artifact.java │ │ │ └── ArtifactManager.java │ │ │ ├── commands │ │ │ ├── ArgCommand.java │ │ │ ├── CmdCommand.java │ │ │ ├── Command.java │ │ │ ├── Comment.java │ │ │ ├── CopyCommand.java │ │ │ ├── EntryPointCommand.java │ │ │ ├── EnvCommand.java │ │ │ ├── ExposeCommand.java │ │ │ ├── FromCommand.java │ │ │ ├── LabelCommand.java │ │ │ ├── MicrodnfCommand.java │ │ │ ├── MultiCommands.java │ │ │ ├── NoopCommand.java │ │ │ ├── PackageCommand.java │ │ │ ├── RunCommand.java │ │ │ ├── RunExecCommand.java │ │ │ ├── UserCommand.java │ │ │ └── WorkDirCommand.java │ │ │ ├── installers │ │ │ ├── FailingPackageManager.java │ │ │ ├── MicroDnf.java │ │ │ ├── PackageManager.java │ │ │ └── PackageManagers.java │ │ │ ├── modules │ │ │ ├── AbstractModule.java │ │ │ ├── GraalVMModule.java │ │ │ ├── GradleModule.java │ │ │ ├── MandrelModule.java │ │ │ ├── MavenModule.java │ │ │ ├── QuarkusDirectoryModule.java │ │ │ ├── QuarkusUserModule.java │ │ │ ├── UpxModule.java │ │ │ └── UsLangModule.java │ │ │ └── utils │ │ │ ├── Commands.java │ │ │ └── Exec.java │ └── resources │ │ └── maven │ │ ├── configure-maven.sh │ │ ├── configure.sh │ │ └── settings.xml │ └── test │ └── java │ └── io │ └── quarkus │ └── images │ ├── Builders.java │ ├── DistrolessTest.java │ ├── MandrelMultiArchTest.java │ ├── MavenAndGradleTest.java │ ├── MultiMicroTest.java │ └── SimpleTest.java ├── pom.xml ├── quarkus-binary-s2i ├── pom.xml └── src │ └── main │ ├── java │ └── io │ │ └── quarkus │ │ └── images │ │ ├── BinaryS2IModule.java │ │ ├── Build.java │ │ ├── Push.java │ │ └── QuarkusBinaryS2I.java │ └── resources │ └── scripts │ ├── assemble │ ├── run │ └── usage ├── quarkus-distroless-base-image ├── pom.xml └── src │ └── main │ └── java │ └── io │ └── quarkus │ └── images │ ├── Build.java │ ├── Push.java │ └── QuarkusDistroless.java ├── quarkus-graalvm-builder-image ├── graalvm.yaml ├── pom.xml └── src │ └── main │ └── java │ └── io │ └── quarkus │ └── images │ ├── Build.java │ ├── Push.java │ └── QuarkusGraalVMBuilder.java ├── quarkus-mandrel-builder-image ├── mandrel-dev.yaml ├── mandrel.yaml ├── pom.xml └── src │ └── main │ └── java │ └── io │ └── quarkus │ └── images │ ├── Build.java │ ├── JenkinsDownloader.java │ ├── Push.java │ ├── QuarkusMandrelBuilder.java │ └── Test.java ├── quarkus-micro-base-image ├── pom.xml └── src │ └── main │ └── java │ └── io │ └── quarkus │ └── images │ ├── Build.java │ ├── Push.java │ └── QuarkusMicro.java └── quarkus-native-s2i ├── graalvm.yaml ├── pom.xml └── src └── main ├── java └── io │ └── quarkus │ └── images │ ├── Build.java │ ├── NativeS2IModule.java │ ├── Push.java │ └── QuarkusNativeS2IBuilder.java └── resources └── scripts ├── assemble └── run /.github/workflows/build-images.yml: -------------------------------------------------------------------------------- 1 | name: Build and Test 2 | # The name of this workflow is significant. Comment bot uses it to 3 | # identify the workflow to comment on, yaml.workflow.name key in 4 | # https://github.com/Karm/QCommenter/blob/main/src/main/resources/application.properties 5 | # or actually in its .env file. 6 | 7 | on: 8 | pull_request: 9 | branches: 10 | - main 11 | push: 12 | branches: 13 | - main 14 | paths-ignore: 15 | - '.build/**' 16 | 17 | jobs: 18 | build-images: 19 | name: Build 20 | runs-on: ubuntu-latest 21 | strategy: 22 | fail-fast: false 23 | matrix: 24 | include: 25 | - name: "base images" 26 | modules: "quarkus-distroless-base-image,quarkus-micro-base-image" 27 | args: "" 28 | - name: "s2i binary images" 29 | modules: "quarkus-binary-s2i" 30 | args: "" 31 | - name: "s2i native images" 32 | modules: "quarkus-native-s2i" 33 | args: "" 34 | - name: "mandrel builder images (latest)" 35 | modules: "quarkus-mandrel-builder-image" 36 | args: "" 37 | - name: "mandrel builder images (dev)" 38 | modules: "quarkus-mandrel-builder-image" 39 | args: "-Pdev" 40 | - name: "graalvm ce builder images (latest)" 41 | modules: "quarkus-graalvm-builder-image" 42 | args: "" 43 | steps: 44 | - name: Re-claim some disk space 45 | run: | 46 | sudo swapoff -a 47 | sudo rm -rf /swapfile /usr/share/dotnet /usr/local/lib/android \ 48 | /usr/local/share/powershell /usr/share/swift /usr/local/.ghcup 49 | sudo apt-get clean 50 | yes | docker system prune -a 51 | df -h 52 | - uses: actions/checkout@v1 53 | - uses: actions/setup-java@v3 54 | with: 55 | distribution: 'temurin' 56 | java-version: '17' 57 | - name: Set up QEMU 58 | uses: docker/setup-qemu-action@v3 59 | with: 60 | platforms: amd64,arm64 61 | - name: Set up Docker Buildx 62 | id: buildx 63 | uses: docker/setup-buildx-action@v3 64 | with: 65 | install: true 66 | - name: Inspect builder 67 | run: | 68 | echo "Name: ${{ steps.buildx.outputs.name }}" 69 | echo "Endpoint: ${{ steps.buildx.outputs.endpoint }}" 70 | echo "Status: ${{ steps.buildx.outputs.status }}" 71 | echo "Flags: ${{ steps.buildx.outputs.flags }}" 72 | echo "Platforms: ${{ steps.buildx.outputs.platforms }}" 73 | - name: Build ${{ matrix.name }} 74 | run: mvn install --batch-mode --projects ${{ matrix.modules }} --also-make ${{ matrix.args }} 75 | - name: Test ${{ matrix.name }} 76 | env: 77 | DOCKER_GHA_BUILDX: true 78 | run: | 79 | set -x 80 | set +e 81 | # Deal with spaces in image descriptions 82 | NM="${{ matrix.name }}" 83 | NM="${NM// /_}" 84 | # DOCKER_GHA_SUMMARY_NAME is used in the testsuite 85 | export DOCKER_GHA_SUMMARY_NAME=testsuite-logs-${NM}.txt 86 | echo "DOCKER_GHA_SUMMARY_NAME=${DOCKER_GHA_SUMMARY_NAME}" >> "$GITHUB_ENV" 87 | EXIT_CODE=0 88 | # GraalVM CE builder images should work too, merely saving time here... 89 | if [[ "${{ matrix.modules }}" == quarkus-mandrel* ]]; then 90 | echo "┌── Testing ${{ matrix.name }}" >> ${DOCKER_GHA_SUMMARY_NAME} 91 | mvn install --batch-mode --projects ${{ matrix.modules }} --also-make -DskipTests -Ptest ${{ matrix.args }} 92 | EXIT_CODE=$? 93 | if [[ $EXIT_CODE -ne 0 ]]; then 94 | echo "└── Done with errors: ${{ matrix.name }}" >> ${DOCKER_GHA_SUMMARY_NAME} 95 | else 96 | echo "└── Done: ${{ matrix.name }}" >> ${DOCKER_GHA_SUMMARY_NAME} 97 | fi 98 | else 99 | echo "═╡ SKIPPED: Testing ${{ matrix.name }}" >> ${DOCKER_GHA_SUMMARY_NAME} 100 | fi 101 | exit $EXIT_CODE 102 | - name: Upload test summary 103 | if: always() 104 | uses: actions/upload-artifact@v4 105 | with: 106 | name: ${{ env.DOCKER_GHA_SUMMARY_NAME }} 107 | path: ${{ github.workspace }}/${{ env.DOCKER_GHA_SUMMARY_NAME }} 108 | - name: Print ${{ matrix.name}} 109 | if: always() 110 | run: docker images 111 | 112 | post-process-summary: 113 | name: Summary 114 | runs-on: ubuntu-latest 115 | needs: build-images 116 | if: always() 117 | steps: 118 | - name: Download all test summaries 119 | if: always() 120 | uses: actions/download-artifact@v4 121 | with: 122 | path: all-logs 123 | - name: Aggregate summaries 124 | if: always() 125 | run: | 126 | echo "Aggregated Summary for builder images" > final-summary.txt 127 | echo "=====================================" >> final-summary.txt 128 | for file in all-logs/*/*.txt; do 129 | cat "$file" >> final-summary.txt 130 | done 131 | - name: Upload final aggregated summary 132 | if: always() 133 | uses: actions/upload-artifact@v4 134 | with: 135 | name: final-summary-log 136 | path: final-summary.txt 137 | - name: Prepare and upload comment data 138 | if: always() && github.event_name == 'pull_request' 139 | run: | 140 | PR_NUMBER=$(jq --raw-output .pull_request.number "$GITHUB_EVENT_PATH") 141 | if [[ -z "$PR_NUMBER" || "$PR_NUMBER" == "null" ]]; then 142 | echo "No PR found. No action." 143 | exit 0 144 | fi 145 | ORG_NAME=$(jq --raw-output .repository.owner.login "$GITHUB_EVENT_PATH") 146 | REPO_NAME=$(jq --raw-output .repository.name "$GITHUB_EVENT_PATH") 147 | COMMENT_JSON_STRING=$(jq -Rs '.' < final-summary.txt) 148 | echo "{\"pr_number\":\"$PR_NUMBER\",\"org\":\"$ORG_NAME\",\"repo\":\"$REPO_NAME\",\"comment_body\":$COMMENT_JSON_STRING}" > comment-data.json 149 | - name: Upload comment data 150 | if: always() && github.event_name == 'pull_request' 151 | uses: actions/upload-artifact@v4 152 | with: 153 | name: comment-data-${{ github.event.pull_request.number }} 154 | path: comment-data.json 155 | -------------------------------------------------------------------------------- /.github/workflows/dev-images.yml: -------------------------------------------------------------------------------- 1 | name: Push Dev Images to Quay 2 | on: 3 | deployment: 4 | schedule: 5 | - cron: '0 0 * * 0' 6 | workflow_dispatch: 7 | 8 | jobs: 9 | build-images: 10 | name: Dev images 11 | runs-on: ubuntu-latest 12 | strategy: 13 | fail-fast: false 14 | matrix: 15 | include: 16 | - name: "mandrel builder images (dev)" 17 | modules: "quarkus-mandrel-builder-image" 18 | args: "-Pdev" 19 | 20 | steps: 21 | - uses: actions/checkout@v1 22 | - uses: actions/setup-java@v3 23 | with: 24 | distribution: 'temurin' 25 | java-version: '17' 26 | - name: Set up QEMU 27 | uses: docker/setup-qemu-action@v3 28 | with: 29 | platforms: amd64,arm64 30 | - name: Set up Docker Buildx 31 | id: buildx 32 | uses: docker/setup-buildx-action@v3 33 | with: 34 | install: true 35 | - name: Inspect builder 36 | run: | 37 | echo "Name: ${{ steps.buildx.outputs.name }}" 38 | echo "Endpoint: ${{ steps.buildx.outputs.endpoint }}" 39 | echo "Status: ${{ steps.buildx.outputs.status }}" 40 | echo "Flags: ${{ steps.buildx.outputs.flags }}" 41 | echo "Platforms: ${{ steps.buildx.outputs.platforms }}" 42 | - name: Build ${{ matrix.name }} 43 | run: mvn install --batch-mode --projects ${{ matrix.modules }} --also-make -DskipTests ${{ matrix.args }} 44 | - name: Test ${{ matrix.name }} 45 | env: 46 | DOCKER_GHA_BUILDX: true 47 | run: | 48 | set -x 49 | set +e 50 | # Deal with spaces in image descriptions 51 | NM="${{ matrix.name }}" 52 | NM="${NM// /_}" 53 | # DOCKER_GHA_SUMMARY_NAME is used in the testsuite 54 | export DOCKER_GHA_SUMMARY_NAME=testsuite-logs-${NM}.txt 55 | echo "DOCKER_GHA_SUMMARY_NAME=${DOCKER_GHA_SUMMARY_NAME}" >> "$GITHUB_ENV" 56 | EXIT_CODE=0 57 | if [[ "${{ matrix.modules }}" == quarkus-mandrel* ]]; then 58 | echo "┌── Testing ${{ matrix.name }}" >> ${DOCKER_GHA_SUMMARY_NAME} 59 | docker run -t dev-amd64 native-image --version 60 | mvn install --batch-mode --projects ${{ matrix.modules }} --also-make -DskipTests -Ptest ${{ matrix.args }} 61 | EXIT_CODE=$? 62 | if [[ $EXIT_CODE -ne 0 ]]; then 63 | echo "└── Done with errors: ${{ matrix.name }}" >> ${DOCKER_GHA_SUMMARY_NAME} 64 | else 65 | echo "└── Done: ${{ matrix.name }}" >> ${DOCKER_GHA_SUMMARY_NAME} 66 | fi 67 | else 68 | echo "═╡ SKIPPED: Testing ${{ matrix.name }}" >> ${DOCKER_GHA_SUMMARY_NAME} 69 | fi 70 | exit $EXIT_CODE 71 | - name: Upload test summary 72 | if: always() 73 | uses: actions/upload-artifact@v4 74 | with: 75 | name: ${{ env.DOCKER_GHA_SUMMARY_NAME }} 76 | path: ${{ github.workspace }}/${{ env.DOCKER_GHA_SUMMARY_NAME }} 77 | - name: Print results ${{ matrix.name}} 78 | if: always() 79 | run: | 80 | docker images 81 | cat '${{ github.workspace }}/${{ env.DOCKER_GHA_SUMMARY_NAME }}' 82 | - name: Login to Container Registry 83 | uses: docker/login-action@v2 84 | with: 85 | registry: quay.io 86 | username: ${{ secrets.QUAY_USER }} 87 | password: ${{ secrets.QUAY_TOKEN }} 88 | - name: Push ${{ matrix.name }} 89 | run: mvn install --batch-mode --projects ${{ matrix.modules }} -DskipTests -Ppush ${{ matrix.args }} 90 | -------------------------------------------------------------------------------- /.github/workflows/push-images.yml: -------------------------------------------------------------------------------- 1 | name: Push Images to Quay 2 | on: 3 | deployment: 4 | schedule: 5 | - cron: '0 0 * * 0' 6 | workflow_dispatch: 7 | 8 | jobs: 9 | build-images: 10 | name: Build and Push images 11 | runs-on: ubuntu-latest 12 | strategy: 13 | fail-fast: false 14 | matrix: 15 | include: 16 | - name: "base images" 17 | modules: "quarkus-distroless-base-image,quarkus-micro-base-image" 18 | args: "" 19 | - name: "s2i binary images" 20 | modules: "quarkus-binary-s2i" 21 | args: "" 22 | - name: "s2i native images" 23 | modules: "quarkus-native-s2i" 24 | args: "" 25 | - name: "mandrel builder images (latest)" 26 | modules: "quarkus-mandrel-builder-image" 27 | args: "" 28 | - name: "graalvm ce builder images (latest)" 29 | modules: "quarkus-graalvm-builder-image" 30 | args: "" 31 | steps: 32 | - name: Re-claim some disk space 33 | run: | 34 | sudo swapoff -a 35 | sudo rm -rf /swapfile /usr/share/dotnet /usr/local/lib/android \ 36 | /usr/local/share/powershell /usr/share/swift /usr/local/.ghcup 37 | sudo apt-get clean 38 | yes | docker system prune -a 39 | df -h 40 | - uses: actions/checkout@v1 41 | - uses: actions/setup-java@v3 42 | with: 43 | distribution: 'temurin' 44 | java-version: '17' 45 | - name: Set up QEMU 46 | uses: docker/setup-qemu-action@v3 47 | with: 48 | platforms: amd64,arm64 49 | - name: Set up Docker Buildx 50 | id: buildx 51 | uses: docker/setup-buildx-action@v3 52 | with: 53 | install: true 54 | - name: Inspect builder 55 | run: | 56 | echo "Name: ${{ steps.buildx.outputs.name }}" 57 | echo "Endpoint: ${{ steps.buildx.outputs.endpoint }}" 58 | echo "Status: ${{ steps.buildx.outputs.status }}" 59 | echo "Flags: ${{ steps.buildx.outputs.flags }}" 60 | echo "Platforms: ${{ steps.buildx.outputs.platforms }}" 61 | - name: Login to GitHub Container Registry 62 | uses: docker/login-action@v2 63 | with: 64 | registry: quay.io 65 | username: ${{ secrets.QUAY_USER }} 66 | password: ${{ secrets.QUAY_TOKEN }} 67 | - name: Build images 68 | run: mvn install --batch-mode --projects ${{ matrix.modules }} --also-make -DskipTests -Ppush ${{ matrix.args }} 69 | - name: Print ${{ matrix.name}} 70 | if: always() 71 | run: | 72 | df -h 73 | docker images 74 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | pom.xml.tag 3 | pom.xml.releaseBackup 4 | pom.xml.versionsBackup 5 | pom.xml.next 6 | release.properties 7 | dependency-reduced-pom.xml 8 | buildNumber.properties 9 | .mvn/timing.properties 10 | mandrel-integration-tests/ 11 | 12 | # Avoid ignoring Maven wrapper jar file (.jar files are usually ignored) 13 | !/.mvn/wrapper/maven-wrapper.jar 14 | .cekit 15 | 16 | .vscode/ 17 | 18 | *~s2i 19 | s2i 20 | 21 | .idea 22 | *.iml 23 | 24 | .jbang/ 25 | -------------------------------------------------------------------------------- /jdock-variant-helper/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | 8 | io.quarkus.images 9 | quarkus-images-parent 10 | 1.0-SNAPSHOT 11 | 12 | 13 | jdock-variant-helper 14 | 15 | 16 | 17 | io.quarkus.images 18 | jdock 19 | 20 | 21 | com.fasterxml.jackson.dataformat 22 | jackson-dataformat-yaml 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /jdock-variant-helper/src/main/java/io/quarkus/images/config/Config.java: -------------------------------------------------------------------------------- 1 | package io.quarkus.images.config; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import com.fasterxml.jackson.dataformat.yaml.YAMLMapper; 5 | 6 | import java.io.File; 7 | import java.io.IOException; 8 | import java.util.Collections; 9 | import java.util.HashMap; 10 | import java.util.List; 11 | import java.util.Map; 12 | import java.util.stream.Collectors; 13 | 14 | public class Config { 15 | 16 | public String image; 17 | public List images; 18 | 19 | public static Config read(String image, File in) throws IOException { 20 | // Read the graalvm.yaml file 21 | YAMLMapper mapper = new YAMLMapper(); 22 | Config config = mapper.readerFor(Config.class).readValue(in); 23 | config.image = image; 24 | return config; 25 | } 26 | 27 | public static class ImageConfig { 28 | @JsonProperty("graalvm-version") // Optional, can be null. 29 | public String graalvmVersion; 30 | @JsonProperty("java-version") 31 | public String javaVersion; 32 | 33 | // Optional, can be null 34 | public String tag; 35 | 36 | // Optional, can be null 37 | public String tags; 38 | public List variants; 39 | 40 | public String graalvmVersion() { 41 | return graalvmVersion; 42 | } 43 | 44 | public List tags() { 45 | if (tag != null) { 46 | if (tags == null) { 47 | return List.of(tag); 48 | } else { 49 | throw new IllegalArgumentException("Cannot use `tag` and `tags` at the same time."); 50 | } 51 | } else if (tags != null) { 52 | return Tag.parse(tags); 53 | } else { 54 | return Collections.emptyList(); 55 | } 56 | } 57 | 58 | public boolean isMultiArch() { 59 | return variants.size() > 1; 60 | } 61 | 62 | public String fullname(Config config, Variant variant) { 63 | if (graalvmVersion != null) { 64 | final String n; 65 | if ("master".equals(graalvmVersion)) { 66 | n = config.image + ":dev"; 67 | } else { 68 | n = config.image + ":" + graalvmVersion + "-java" + javaVersion; 69 | } 70 | if (variant != null && variant.arch() != null) { 71 | return n + "-" + variant.arch(); 72 | } 73 | return n; 74 | } else { 75 | if (variant != null && variant.arch() != null) { 76 | return config.image + ":jdk-" + javaVersion + "-" + variant.arch(); 77 | } 78 | return config.image + ":jdk-" + javaVersion; 79 | } 80 | } 81 | 82 | public String fullname(Config config) { 83 | return fullname(config, null); 84 | } 85 | 86 | public Map getArchToImage(Config config) { 87 | Map result = new HashMap<>(); 88 | for (Variant variant : variants) { 89 | result.put(variant.arch(), fullname(config, variant)); 90 | } 91 | return result; 92 | } 93 | 94 | public List getNestedImages(Config config) { 95 | return variants.stream().map(v -> fullname(config, v)).collect(Collectors.toList()); 96 | } 97 | 98 | public String javaVersion() { 99 | return javaVersion; 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /jdock-variant-helper/src/main/java/io/quarkus/images/config/Tag.java: -------------------------------------------------------------------------------- 1 | package io.quarkus.images.config; 2 | 3 | import io.quarkus.images.MultiArchImage; 4 | import io.quarkus.images.utils.Exec; 5 | 6 | import java.util.Arrays; 7 | import java.util.List; 8 | import java.util.stream.Collectors; 9 | 10 | public class Tag { 11 | 12 | private Tag() { 13 | // Avoid direct instantiation. 14 | } 15 | 16 | public static void createTagsIfAny(Config config, Config.ImageConfig img, boolean push) { 17 | createTagsIfAny(config, img, push, null); 18 | } 19 | 20 | public static void createTagsIfAny(Config config, Config.ImageConfig img, boolean push, int[] jdkVersion) { 21 | img.tags().forEach(tag -> { 22 | if (tag.startsWith("jdk-%")) { 23 | if (jdkVersion == null) { 24 | throw new IllegalStateException("Unable to create tag " + tag + " as the JDK jdkVersion is not set"); 25 | } 26 | tag = String.format(tag, jdkVersion[0], jdkVersion[1], jdkVersion[2], jdkVersion[3]); 27 | } 28 | final String fn = config.image + ":" + tag; 29 | if (img.isMultiArch()) { 30 | if (!push) { 31 | System.out.println("\uD83D\uDD25\tSkipping the creation of the tag " + fn + " as push is set to false"); 32 | return; 33 | } 34 | MultiArchImage.createAndPushManifest(fn, img.getArchToImage(config)); 35 | } else { 36 | final String src = img.fullname(config, img.variants.get(0)); 37 | System.out.println("\uD83D\uDD25\tCreating tag " + fn + " => " + src); 38 | Exec.execute(List.of("docker", "tag", src, fn), 39 | e -> new RuntimeException("Unable to create tag for " + src, e)); 40 | if (push) { 41 | Exec.execute(List.of("docker", "push", fn), 42 | e -> new RuntimeException("Unable to push tag " + fn)); 43 | } 44 | } 45 | }); 46 | } 47 | 48 | /** 49 | * Comma-separated list of tags. 50 | * 51 | * @param tags the list 52 | * @return the parsed list 53 | */ 54 | public static List parse(String tags) { 55 | String[] segments = tags.split(","); 56 | return Arrays.stream(segments) 57 | .map(String::trim) 58 | .filter(s -> !s.isBlank()) 59 | .collect(Collectors.toList()); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /jdock-variant-helper/src/main/java/io/quarkus/images/config/Variant.java: -------------------------------------------------------------------------------- 1 | package io.quarkus.images.config; 2 | 3 | public record Variant( 4 | String arch, 5 | String sha) { 6 | 7 | } 8 | -------------------------------------------------------------------------------- /jdock/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | 8 | io.quarkus.images 9 | quarkus-images-parent 10 | 1.0-SNAPSHOT 11 | 12 | 13 | jdock 14 | 15 | -------------------------------------------------------------------------------- /jdock/src/main/java/io/quarkus/images/BuildContext.java: -------------------------------------------------------------------------------- 1 | package io.quarkus.images; 2 | 3 | import io.quarkus.images.artifacts.Artifact; 4 | import io.quarkus.images.artifacts.ArtifactManager; 5 | import io.quarkus.images.commands.*; 6 | import io.quarkus.images.installers.PackageManager; 7 | import io.quarkus.images.installers.PackageManagers; 8 | import io.quarkus.images.modules.AbstractModule; 9 | import io.quarkus.images.utils.Commands; 10 | 11 | import java.io.File; 12 | import java.util.ArrayList; 13 | import java.util.List; 14 | 15 | public class BuildContext { 16 | private final List commands = new ArrayList<>(); 17 | 18 | private final File basedir; 19 | private String currentUser; 20 | 21 | private PackageManager packages; 22 | private final ArtifactManager artifacts; 23 | private String alias; 24 | 25 | private int[] jdkVersion = new int[4]; 26 | 27 | public BuildContext(File basedir, String from, String platform) { 28 | this.basedir = basedir; 29 | this.packages = PackageManagers.guess(from); 30 | this.artifacts = new ArtifactManager(); 31 | commands.add(new FromCommand(from, platform)); 32 | } 33 | 34 | public BuildContext(File basedir) { 35 | this.basedir = basedir; 36 | this.packages = null; 37 | this.artifacts = new ArtifactManager(); 38 | } 39 | 40 | public void add(AbstractModule module) { 41 | commands.add(Comment.comment("Module %s", module.toString())); 42 | commands.add(module); 43 | commands.add(Comment.comment("----------------")); 44 | } 45 | 46 | public void add(Command command) { 47 | commands.add(command); 48 | } 49 | 50 | public Artifact addArtifact(Artifact artifact) { 51 | artifacts.download(artifact); 52 | return artifact; 53 | } 54 | 55 | public String build() { 56 | return build(false); 57 | } 58 | 59 | public String build(boolean skipCleanup) { 60 | ArrayList copy = new ArrayList<>(commands); 61 | if (!skipCleanup) { 62 | // Introduce cleanup 63 | // The cleanup module must be introduced before the last `USER` command or before the last command 64 | int lastUserIndex = Commands.lastUserCommand(copy); 65 | 66 | Command cleanup = cleanup(); 67 | if (lastUserIndex == -1) { 68 | copy.add(copy.size() - 1, cleanup); 69 | } else { 70 | copy.add(lastUserIndex, cleanup); 71 | } 72 | } 73 | StringBuilder buffer = new StringBuilder(); 74 | 75 | for (Command command : copy) { 76 | String output = command.execute(this); 77 | if (output != null && !output.isEmpty()) { 78 | buffer.append(output).append("\n"); 79 | } 80 | } 81 | 82 | return buffer.toString(); 83 | } 84 | 85 | public void setCurrentUser(String user) { 86 | this.currentUser = user; 87 | } 88 | 89 | public void setPackageManager(String packageManager) { 90 | this.packages = PackageManagers.find(packageManager); 91 | } 92 | 93 | public boolean isCurrentUserRoot() { 94 | return UserCommand.ROOT.equalsIgnoreCase(this.currentUser); 95 | } 96 | 97 | public String getCurrentUser() { 98 | return this.currentUser; 99 | } 100 | 101 | public void install(String... packages) { 102 | add(getPackageManager().install(packages)); 103 | } 104 | 105 | public Command cleanup() { 106 | return (MultiCommands) bc -> { 107 | List cmd = new ArrayList<>(); 108 | if (packages.hasBeenUsed() || !artifacts.isEmpty()) { 109 | cmd.add(Comment.comment("Cleanup the file system")); 110 | } 111 | String current = bc.getCurrentUser(); 112 | if (current != null && !bc.isCurrentUserRoot()) { 113 | cmd.add(UserCommand.root()); 114 | } 115 | Command cleanup = packages.cleanup(); 116 | if (!(cleanup instanceof NoopCommand)) { 117 | cmd.add(cleanup); 118 | } 119 | 120 | if (!artifacts.isEmpty()) { 121 | cmd.add(new RunCommand("[ ! -d /tmp/artifacts ] || rm -rf /tmp/artifacts")); 122 | } 123 | if (current != null && !UserCommand.ROOT.equalsIgnoreCase(current)) { 124 | cmd.add(UserCommand.user(current)); 125 | } 126 | if (packages.hasBeenUsed() || !artifacts.isEmpty()) { 127 | cmd.add(Comment.comment("----------------")); 128 | } 129 | return cmd; 130 | }; 131 | 132 | } 133 | 134 | public PackageManager getPackageManager() { 135 | return packages; 136 | } 137 | 138 | public void setAlias(String alias) { 139 | this.alias = alias; 140 | } 141 | 142 | public String getAlias() { 143 | return alias; 144 | } 145 | 146 | public File getBasedir() { 147 | return basedir; 148 | } 149 | 150 | public int[] getJdkVersion() { 151 | return jdkVersion; 152 | } 153 | 154 | public void setJdkVersion(int[] jdkVersion) { 155 | this.jdkVersion = jdkVersion; 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /jdock/src/main/java/io/quarkus/images/Buildable.java: -------------------------------------------------------------------------------- 1 | package io.quarkus.images; 2 | 3 | import java.io.File; 4 | 5 | public interface Buildable { 6 | 7 | String build(); 8 | 9 | void build(File output); 10 | 11 | void buildLocalImage(String imageName, boolean dryRun); 12 | 13 | void buildAndPush(String imageName); 14 | } 15 | -------------------------------------------------------------------------------- /jdock/src/main/java/io/quarkus/images/Dockerfile.java: -------------------------------------------------------------------------------- 1 | package io.quarkus.images; 2 | 3 | import io.quarkus.images.commands.*; 4 | import io.quarkus.images.modules.AbstractModule; 5 | import io.quarkus.images.utils.Exec; 6 | 7 | import java.io.File; 8 | import java.io.IOException; 9 | import java.io.UncheckedIOException; 10 | import java.nio.file.Files; 11 | 12 | public class Dockerfile implements Buildable { 13 | 14 | protected BuildContext context; 15 | 16 | public static Dockerfile from(String base) { 17 | return new Dockerfile(JDock.basedir, base, null); 18 | } 19 | 20 | public static Dockerfile from(String base, String platform) { 21 | return new Dockerfile(JDock.basedir, base, platform); 22 | } 23 | 24 | Dockerfile(File basedir, String from, String platform) { 25 | context = new BuildContext(basedir, from, platform); 26 | } 27 | 28 | Dockerfile(File basedir) { 29 | context = new BuildContext(basedir); 30 | } 31 | 32 | public static MultiStageDockerFile multistages() { 33 | return new MultiStageDockerFile(); 34 | } 35 | 36 | public Dockerfile run(String... args) { 37 | context.add(new RunCommand(args)); 38 | return this; 39 | } 40 | 41 | public Dockerfile exec(String... args) { 42 | context.add(new RunExecCommand(args)); 43 | return this; 44 | } 45 | 46 | public Dockerfile user(String user) { 47 | context.add(UserCommand.user(user)); 48 | return this; 49 | } 50 | 51 | public Dockerfile install(String... packages) { 52 | context.install(packages); 53 | return this; 54 | } 55 | 56 | public String build() { 57 | return context.build(); 58 | } 59 | 60 | public void build(File out) { 61 | try { 62 | Files.writeString(out.toPath(), context.build()); 63 | } catch (IOException e) { 64 | throw new UncheckedIOException(e); 65 | } 66 | } 67 | 68 | public Dockerfile installer(String installer) { 69 | context.setPackageManager(installer); 70 | return this; 71 | } 72 | 73 | public Dockerfile env(String... env) { 74 | context.add(new EnvCommand(env)); 75 | return this; 76 | } 77 | 78 | public Dockerfile label(String... lb) { 79 | context.add(new LabelCommand(lb)); 80 | return this; 81 | } 82 | 83 | public Dockerfile module(AbstractModule module) { 84 | context.add(module); 85 | return this; 86 | } 87 | 88 | public Dockerfile workdir(String path) { 89 | context.add(new WorkDirCommand(path)); 90 | return this; 91 | } 92 | 93 | public Dockerfile entrypoint(String cmd) { 94 | context.add(new EntryPointCommand(cmd)); 95 | return this; 96 | } 97 | 98 | public Dockerfile arg(String arg) { 99 | context.add(new ArgCommand(arg)); 100 | return this; 101 | } 102 | 103 | public Dockerfile stage(String base) { 104 | context.add(new FromCommand(base, null)); 105 | return this; 106 | } 107 | 108 | public Dockerfile copyFromStage(String alias, String source, String dest) { 109 | context.add(new CopyCommand(alias, source, dest)); 110 | return this; 111 | } 112 | 113 | public Dockerfile copyFromStage(String alias, String source) { 114 | context.add(new CopyCommand(alias, source, source)); 115 | return this; 116 | } 117 | 118 | @Override 119 | public void buildLocalImage(String imageName, boolean dryRun) { 120 | Exec.buildLocal(this, imageName, "amd64", dryRun); 121 | } 122 | 123 | @Override 124 | public void buildAndPush(String imageName) { 125 | Exec.buildAndPush(this, imageName, "amd64"); 126 | } 127 | 128 | public Dockerfile expose(int port) { 129 | context.add(new ExposeCommand(port)); 130 | return this; 131 | } 132 | 133 | public Dockerfile cmd(String cmd) { 134 | context.add(new CmdCommand(cmd)); 135 | return this; 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /jdock/src/main/java/io/quarkus/images/JDock.java: -------------------------------------------------------------------------------- 1 | package io.quarkus.images; 2 | 3 | import java.io.File; 4 | 5 | public class JDock { 6 | 7 | public static void setDockerFileDir(File dir) { 8 | if (dir == null) { 9 | throw new IllegalArgumentException("The directory must not be null"); 10 | } 11 | if (!dir.isDirectory()) { 12 | dir.mkdirs(); 13 | } 14 | dockerFileDir = dir.getAbsolutePath(); 15 | } 16 | 17 | public static String dockerFileDir = "."; 18 | 19 | public static File basedir = new File(dockerFileDir); 20 | } 21 | -------------------------------------------------------------------------------- /jdock/src/main/java/io/quarkus/images/MultiArchImage.java: -------------------------------------------------------------------------------- 1 | package io.quarkus.images; 2 | 3 | import io.quarkus.images.utils.Exec; 4 | 5 | import java.io.File; 6 | import java.util.ArrayList; 7 | import java.util.Arrays; 8 | import java.util.Collections; 9 | import java.util.HashMap; 10 | import java.util.List; 11 | import java.util.Map; 12 | 13 | public class MultiArchImage { 14 | 15 | private final Map images; 16 | private final String name; 17 | private Map locals = Collections.emptyMap(); 18 | 19 | public MultiArchImage(String name, Map images) { 20 | this.name = name; 21 | this.images = images; 22 | } 23 | 24 | public void buildLocalImages(boolean dryRun) { 25 | if (dryRun) { 26 | System.out.println("⚙️\tGenerating docker files"); 27 | } else { 28 | System.out.println("⚙️\tBuilding local images only (no push)"); 29 | } 30 | _buildLocalImages(dryRun); 31 | } 32 | 33 | private Map _buildLocalImages(boolean dryRun) { 34 | Map built = new HashMap<>(); 35 | // Step 1 - build each image 36 | for (Map.Entry entry : images.entrySet()) { 37 | String arch = entry.getKey(); 38 | Buildable df = entry.getValue(); 39 | 40 | // Create the docker file: 41 | String imageName = name + "-" + arch; 42 | String fileName = imageName.toLowerCase() + ".Dockerfile"; 43 | while (fileName.contains("/")) { 44 | fileName = fileName.substring(fileName.indexOf("/") + 1); 45 | } 46 | File dockerfile = new File(JDock.dockerFileDir + "/" + fileName); 47 | df.build(dockerfile); 48 | 49 | if (!dockerfile.isFile()) { 50 | throw new IllegalStateException("File " + dockerfile.getAbsolutePath() + " does not exist"); 51 | } 52 | 53 | if (!dryRun) { 54 | // docker buildx build --load --platform linux/arm64 --tag cescoffier/mandrel-java17-22.1.0.0-final-arm64 -f mandrel-java17-22.1.0.0-Final-arm64.Dockerfile . 55 | // Build the image (platform-specific) 56 | Exec.execute( 57 | List.of(Exec.getContainerTool(), "buildx", "build", "--load", "--platform=linux/" + arch, "--tag", 58 | imageName, 59 | "-f", JDock.dockerFileDir + "/" + fileName, "."), 60 | e -> new RuntimeException("Unable to build image for " + dockerfile.getAbsolutePath(), e)); 61 | } else { 62 | System.out.println("⚠️️\tSkipping the container build for " + imageName 63 | + " (dry-run), the Dockerfile has been generated in " + dockerfile.getAbsolutePath()); 64 | } 65 | 66 | built.put(arch, imageName); 67 | } 68 | return built; 69 | } 70 | 71 | public void buildAndPush() { 72 | System.out.println("⚙️\tBuilding multi-arch images: " + name); 73 | 74 | // no dry run when pushing images 75 | locals = _buildLocalImages(false); 76 | 77 | System.out.println("⚙️\tPush the images: " + locals.values()); 78 | 79 | for (Map.Entry entry : locals.entrySet()) { 80 | System.out.println("⚙️\tPushing " + entry.getValue() + " (" + entry.getKey() + ")"); 81 | Exec.execute(List.of("docker", "push", entry.getValue()), 82 | e -> new RuntimeException("Unable to push " + entry.getValue(), e)); 83 | } 84 | 85 | createAndPushManifest(name, locals); 86 | } 87 | 88 | public static void createAndPushManifest(String name, Map archToImage) { 89 | System.out.println( 90 | "⚙️\tCreating manifest for " + name + " including the following images:"); 91 | for (Map.Entry entry : archToImage.entrySet()) { 92 | System.out.println( 93 | "⚙️\t\t" + entry.getKey() + " => " + entry.getValue()); 94 | } 95 | 96 | List command = new ArrayList<>(Arrays.asList("docker", "manifest", "create", name)); 97 | for (Map.Entry entry : archToImage.entrySet()) { 98 | command.addAll(List.of("--amend", entry.getValue())); 99 | } 100 | 101 | Exec.execute(command, e -> new RuntimeException("Unable to build manifest for " + name, e)); 102 | 103 | Exec.execute(List.of("docker", "manifest", "push", name), 104 | e -> new RuntimeException("Unable to push manifest for " + name, e)); 105 | 106 | } 107 | 108 | public Map getLocalImages() { 109 | return locals; 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /jdock/src/main/java/io/quarkus/images/MultiStageDockerFile.java: -------------------------------------------------------------------------------- 1 | package io.quarkus.images; 2 | 3 | import io.quarkus.images.utils.Exec; 4 | 5 | import java.io.File; 6 | import java.io.IOException; 7 | import java.io.UncheckedIOException; 8 | import java.nio.file.Files; 9 | import java.util.LinkedHashMap; 10 | import java.util.Map; 11 | 12 | public class MultiStageDockerFile implements Buildable { 13 | 14 | Map stages = new LinkedHashMap<>(); 15 | 16 | public MultiStageDockerFile stage(String alias, Dockerfile df) { 17 | df.context.setAlias(alias); 18 | stages.put(alias, df); 19 | return this; 20 | } 21 | 22 | public MultiStageDockerFile stage(Dockerfile df) { 23 | stages.put("", df); // Last stage 24 | return this; 25 | } 26 | 27 | public String build() { 28 | StringBuilder content = new StringBuilder(); 29 | for (Map.Entry entry : stages.entrySet()) { 30 | if (!entry.getKey().isEmpty()) { 31 | content.append("# -- Stage ").append(entry.getKey()).append("\n"); 32 | content.append(entry.getValue().context.build(true)); 33 | } else { 34 | content.append("# -- Final Stage").append("\n"); 35 | content.append(entry.getValue().context.build(false)); 36 | } 37 | } 38 | return content.toString(); 39 | } 40 | 41 | public void build(File out) { 42 | try { 43 | Files.writeString(out.toPath(), build()); 44 | } catch (IOException e) { 45 | throw new UncheckedIOException(e); 46 | } 47 | } 48 | 49 | @Override 50 | public void buildLocalImage(String imageName, boolean dryRun) { 51 | Exec.buildLocal(this, imageName, "amd64", dryRun); 52 | } 53 | 54 | @Override 55 | public void buildAndPush(String imageName) { 56 | Exec.buildAndPush(this, imageName, null); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /jdock/src/main/java/io/quarkus/images/artifacts/Artifact.java: -------------------------------------------------------------------------------- 1 | package io.quarkus.images.artifacts; 2 | 3 | import java.io.File; 4 | 5 | import static io.quarkus.images.artifacts.ArtifactManager.STORE_DIR; 6 | import static io.quarkus.images.artifacts.ArtifactManager.STORE_ROOT; 7 | 8 | public class Artifact { 9 | 10 | public final String name; 11 | public final String url; 12 | public final String sha256; 13 | public final File store; 14 | public final String path; 15 | 16 | public Artifact(String name, String url, String sha256) { 17 | this.name = name; 18 | this.url = url; 19 | this.sha256 = sha256; 20 | 21 | this.store = new File(STORE_ROOT, name); 22 | this.path = STORE_DIR + name; 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /jdock/src/main/java/io/quarkus/images/artifacts/ArtifactManager.java: -------------------------------------------------------------------------------- 1 | package io.quarkus.images.artifacts; 2 | 3 | import com.google.common.hash.Hashing; 4 | import com.google.common.io.Files; 5 | 6 | import java.io.File; 7 | import java.io.FileOutputStream; 8 | import java.io.IOException; 9 | import java.io.UncheckedIOException; 10 | import java.net.URL; 11 | import java.nio.channels.Channels; 12 | import java.nio.channels.FileChannel; 13 | import java.nio.channels.ReadableByteChannel; 14 | import java.util.ArrayList; 15 | 16 | public class ArtifactManager { 17 | 18 | public static final File STORE_ROOT = new File("target/artifacts"); 19 | public static final String STORE_DIR = "/target/artifacts/"; 20 | static { 21 | if (!STORE_ROOT.isDirectory()) { 22 | STORE_ROOT.mkdirs(); 23 | } 24 | } 25 | 26 | private final ArrayList artifacts; 27 | 28 | public ArtifactManager() { 29 | artifacts = new ArrayList<>(); 30 | } 31 | 32 | public void download(Artifact artifact) { 33 | if (artifact.store.isFile()) { 34 | System.out.println( 35 | "↪️\tSkipping download of " + artifact.name + " - " + artifact.store.getAbsolutePath() + " exists"); 36 | verify(artifact); 37 | return; 38 | } 39 | System.out.println("⬇️\tDownloading " + artifact.name); 40 | try (ReadableByteChannel readableByteChannel = Channels.newChannel(new URL(artifact.url).openStream()); 41 | FileOutputStream fileOutputStream = new FileOutputStream(artifact.store)) { 42 | FileChannel fileChannel = fileOutputStream.getChannel(); 43 | fileChannel 44 | .transferFrom(readableByteChannel, 0, Long.MAX_VALUE); 45 | } catch (IOException e) { 46 | throw new UncheckedIOException(e); 47 | } 48 | 49 | verify(artifact); 50 | } 51 | 52 | public void verify(Artifact artifact) { 53 | if (artifact.sha256 != null) { 54 | System.out.println("\uD83D\uDD0E\tVerifying " + artifact.name + "..."); 55 | String sha = shaSum256(artifact.store); 56 | if (!artifact.sha256.equalsIgnoreCase(sha)) { 57 | throw new IllegalStateException( 58 | "Invalid signature for artifact " + artifact.name + ", " + sha + " != " + artifact.sha256); 59 | } 60 | System.out.println("\uD83C\uDD97\tThe signature of " + artifact.name + " is valid."); 61 | } else { 62 | System.out.println("↪️\tSkipping verification of " + artifact.name + " : no signature"); 63 | } 64 | // Add the artifact to the list 65 | artifacts.add(artifact); 66 | } 67 | 68 | public static String shaSum256(File file) { 69 | try { 70 | return Files.asByteSource(file).hash(Hashing.sha256()).toString(); 71 | } catch (IOException e) { 72 | throw new RuntimeException(String.format("Failed to compute the sha256 of %s", file), e); 73 | } 74 | } 75 | 76 | public boolean isEmpty() { 77 | return artifacts.isEmpty(); 78 | } 79 | 80 | public Artifact local(File file) { 81 | return null; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /jdock/src/main/java/io/quarkus/images/commands/ArgCommand.java: -------------------------------------------------------------------------------- 1 | package io.quarkus.images.commands; 2 | 3 | import io.quarkus.images.BuildContext; 4 | 5 | public class ArgCommand implements Command { 6 | private final String arg; 7 | 8 | public ArgCommand(String arg) { 9 | this.arg = arg; 10 | } 11 | 12 | @Override 13 | public String execute(BuildContext context) { 14 | return "ARG " + arg; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /jdock/src/main/java/io/quarkus/images/commands/CmdCommand.java: -------------------------------------------------------------------------------- 1 | package io.quarkus.images.commands; 2 | 3 | import io.quarkus.images.BuildContext; 4 | 5 | public class CmdCommand implements Command { 6 | 7 | private final String cmd; 8 | 9 | public CmdCommand(String cmd) { 10 | this.cmd = cmd; 11 | } 12 | 13 | @Override 14 | public String execute(BuildContext context) { 15 | return "CMD [\"" + cmd + "\"]"; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /jdock/src/main/java/io/quarkus/images/commands/Command.java: -------------------------------------------------------------------------------- 1 | package io.quarkus.images.commands; 2 | 3 | import io.quarkus.images.BuildContext; 4 | 5 | public interface Command { 6 | 7 | String execute(BuildContext context); 8 | 9 | } 10 | -------------------------------------------------------------------------------- /jdock/src/main/java/io/quarkus/images/commands/Comment.java: -------------------------------------------------------------------------------- 1 | package io.quarkus.images.commands; 2 | 3 | import io.quarkus.images.BuildContext; 4 | 5 | import java.util.stream.Collectors; 6 | 7 | public class Comment implements Command { 8 | 9 | private final String comment; 10 | 11 | public Comment(String comment) { 12 | this.comment = comment; 13 | } 14 | 15 | public static Comment comment(String s, Object... args) { 16 | return new Comment(s.formatted(args)); 17 | } 18 | 19 | @Override 20 | public String execute(BuildContext context) { 21 | return comment.lines() 22 | .map(s -> "# " + s) 23 | .collect(Collectors.joining("\n")); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /jdock/src/main/java/io/quarkus/images/commands/CopyCommand.java: -------------------------------------------------------------------------------- 1 | package io.quarkus.images.commands; 2 | 3 | import io.quarkus.images.BuildContext; 4 | import io.quarkus.images.artifacts.Artifact; 5 | 6 | import java.io.File; 7 | 8 | public class CopyCommand implements Command { 9 | 10 | private final String source; 11 | private final String dest; 12 | 13 | private final String alias; 14 | private final Artifact artifact; 15 | 16 | public CopyCommand(File in, String dest) { 17 | this(null, null, relative(in), dest); 18 | } 19 | 20 | public static String relative(File in) { 21 | return new File(".").toURI().relativize(in.toURI()).getPath(); 22 | } 23 | 24 | public CopyCommand(String in, String dest) { 25 | this(null, null, in, dest); 26 | } 27 | 28 | public CopyCommand(Artifact artifact, String dest) { 29 | this(artifact, null, artifact.path, dest); 30 | } 31 | 32 | public CopyCommand(String alias, String source, String dest) { 33 | this(null, alias, source, dest); 34 | } 35 | 36 | public CopyCommand(Artifact artifact, String alias, String source, String dest) { 37 | this.alias = alias; 38 | this.source = source; 39 | this.dest = dest; 40 | this.artifact = artifact; 41 | } 42 | 43 | @Override 44 | public String execute(BuildContext context) { 45 | if (alias != null) { 46 | return "COPY --from=" + alias + " " + source + " " + dest; 47 | } 48 | if (artifact != null) { 49 | return "# Artifact %s downloaded from %s\nCOPY %s %s".formatted( 50 | artifact.name, artifact.url, 51 | source, dest); 52 | } 53 | return "COPY " + source + " " + dest; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /jdock/src/main/java/io/quarkus/images/commands/EntryPointCommand.java: -------------------------------------------------------------------------------- 1 | package io.quarkus.images.commands; 2 | 3 | import io.quarkus.images.BuildContext; 4 | 5 | public class EntryPointCommand implements Command { 6 | 7 | private final String cmd; 8 | 9 | public EntryPointCommand(String cmd) { 10 | this.cmd = cmd; 11 | 12 | } 13 | 14 | @Override 15 | public String execute(BuildContext context) { 16 | return "ENTRYPOINT [\"" + cmd + "\"]"; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /jdock/src/main/java/io/quarkus/images/commands/EnvCommand.java: -------------------------------------------------------------------------------- 1 | package io.quarkus.images.commands; 2 | 3 | import io.quarkus.images.BuildContext; 4 | 5 | public class EnvCommand implements Command { 6 | 7 | private final String args; 8 | 9 | public EnvCommand(String... env) { 10 | StringBuilder builder = new StringBuilder(); 11 | String name = null; 12 | for (String s : env) { 13 | if (name == null) { 14 | name = s; 15 | } else { 16 | if (builder.length() > 0) { 17 | builder.append(" \\\n "); 18 | } 19 | builder.append(name).append("=\"").append(s).append("\""); 20 | name = null; 21 | } 22 | } 23 | args = builder.toString(); 24 | } 25 | 26 | @Override 27 | public String execute(BuildContext context) { 28 | return "ENV " + args; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /jdock/src/main/java/io/quarkus/images/commands/ExposeCommand.java: -------------------------------------------------------------------------------- 1 | package io.quarkus.images.commands; 2 | 3 | import io.quarkus.images.BuildContext; 4 | 5 | public class ExposeCommand implements Command { 6 | 7 | private final int port; 8 | 9 | public ExposeCommand(int port) { 10 | this.port = port; 11 | } 12 | 13 | @Override 14 | public String execute(BuildContext context) { 15 | return "EXPOSE " + port; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /jdock/src/main/java/io/quarkus/images/commands/FromCommand.java: -------------------------------------------------------------------------------- 1 | package io.quarkus.images.commands; 2 | 3 | import io.quarkus.images.BuildContext; 4 | 5 | import java.util.Objects; 6 | 7 | public class FromCommand implements Command { 8 | private final String command; 9 | 10 | public FromCommand(String from, String platform) { 11 | String line = "FROM "; 12 | 13 | if (platform != null) { 14 | line += "--platform=" + platform + " "; 15 | } 16 | 17 | line += Objects.requireNonNullElse(from, "scratch"); 18 | 19 | this.command = line; 20 | } 21 | 22 | @Override 23 | public String execute(BuildContext context) { 24 | if (context.getAlias() != null) { 25 | return command + " AS " + context.getAlias(); 26 | } 27 | return command; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /jdock/src/main/java/io/quarkus/images/commands/LabelCommand.java: -------------------------------------------------------------------------------- 1 | package io.quarkus.images.commands; 2 | 3 | import io.quarkus.images.BuildContext; 4 | 5 | public class LabelCommand implements Command { 6 | 7 | private final String args; 8 | 9 | public LabelCommand(String... env) { 10 | StringBuilder builder = new StringBuilder(); 11 | String name = null; 12 | for (String s : env) { 13 | if (name == null) { 14 | name = s; 15 | } else { 16 | if (builder.length() > 0) { 17 | builder.append(" \\\n"); 18 | } 19 | builder.append(name).append("=\"").append(s).append("\""); 20 | name = null; 21 | } 22 | } 23 | args = builder.toString(); 24 | } 25 | 26 | @Override 27 | public String execute(BuildContext context) { 28 | return "LABEL " + args; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /jdock/src/main/java/io/quarkus/images/commands/MicrodnfCommand.java: -------------------------------------------------------------------------------- 1 | package io.quarkus.images.commands; 2 | 3 | import io.quarkus.images.BuildContext; 4 | 5 | import java.util.ArrayList; 6 | import java.util.Collections; 7 | import java.util.List; 8 | 9 | public class MicrodnfCommand implements MultiCommands { 10 | 11 | public static final String TEMPLATE = """ 12 | microdnf --setopt=install_weak_deps=0 --setopt=tsflags=nodocs install -y %s \\ 13 | && microdnf clean all \\ 14 | && rpm -q %s"""; 15 | private final List commands = new ArrayList<>(3); 16 | 17 | public MicrodnfCommand(String... packages) { 18 | String list = String.join(" ", packages); 19 | commands.add(new RunCommand(TEMPLATE.formatted(list, list))); 20 | } 21 | 22 | @Override 23 | public List commands(BuildContext bc) { 24 | return commands; 25 | } 26 | 27 | private String originalUser = null; 28 | 29 | @Override 30 | public List before(BuildContext bc) { 31 | if (!bc.isCurrentUserRoot()) { 32 | originalUser = bc.getCurrentUser(); 33 | return List.of(UserCommand.root()); 34 | } 35 | return Collections.emptyList(); 36 | } 37 | 38 | @Override 39 | public List after(BuildContext bc) { 40 | if (originalUser != null) { 41 | return List.of(UserCommand.user(originalUser)); 42 | } 43 | return Collections.emptyList(); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /jdock/src/main/java/io/quarkus/images/commands/MultiCommands.java: -------------------------------------------------------------------------------- 1 | package io.quarkus.images.commands; 2 | 3 | import io.quarkus.images.BuildContext; 4 | 5 | import java.util.ArrayList; 6 | import java.util.Collections; 7 | import java.util.List; 8 | 9 | public interface MultiCommands extends Command { 10 | 11 | List commands(BuildContext bc); 12 | 13 | default List before(BuildContext bc) { 14 | return Collections.emptyList(); 15 | } 16 | 17 | default List after(BuildContext bc) { 18 | return Collections.emptyList(); 19 | } 20 | 21 | @Override 22 | default String execute(BuildContext context) { 23 | StringBuilder buffer = new StringBuilder(); 24 | List all = new ArrayList<>(before(context)); 25 | all.addAll(commands(context)); 26 | all.addAll(after(context)); 27 | if (all.isEmpty()) { 28 | return ""; 29 | } 30 | for (Command command : all) { 31 | String cmd = command.execute(context); 32 | if (cmd != null && !cmd.isEmpty()) { 33 | if (buffer.length() > 0) { 34 | buffer.append("\n"); 35 | } 36 | buffer.append(cmd); 37 | } 38 | } 39 | 40 | return buffer.toString(); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /jdock/src/main/java/io/quarkus/images/commands/NoopCommand.java: -------------------------------------------------------------------------------- 1 | package io.quarkus.images.commands; 2 | 3 | import io.quarkus.images.BuildContext; 4 | 5 | import java.util.Collections; 6 | import java.util.List; 7 | 8 | public class NoopCommand implements MultiCommands { 9 | 10 | public static final NoopCommand INSTANCE = new NoopCommand(); 11 | 12 | private NoopCommand() { 13 | 14 | } 15 | 16 | @Override 17 | public List commands(BuildContext bc) { 18 | return Collections.emptyList(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /jdock/src/main/java/io/quarkus/images/commands/PackageCommand.java: -------------------------------------------------------------------------------- 1 | package io.quarkus.images.commands; 2 | 3 | import io.quarkus.images.BuildContext; 4 | import io.quarkus.images.installers.PackageManager; 5 | 6 | public class PackageCommand implements Command { 7 | 8 | private final String[] packages; 9 | 10 | public PackageCommand(String... packages) { 11 | this.packages = packages; 12 | } 13 | 14 | @Override 15 | public String execute(BuildContext context) { 16 | PackageManager packageManager = context.getPackageManager(); 17 | if (packageManager == null) { 18 | throw new IllegalStateException("Installer not set, you must set up the installer first"); 19 | } 20 | return packageManager.install(packages).execute(context); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /jdock/src/main/java/io/quarkus/images/commands/RunCommand.java: -------------------------------------------------------------------------------- 1 | package io.quarkus.images.commands; 2 | 3 | import io.quarkus.images.BuildContext; 4 | 5 | public class RunCommand implements Command { 6 | 7 | private final String args; 8 | 9 | public RunCommand(String args) { 10 | this.args = args; 11 | } 12 | 13 | public RunCommand(String... commands) { 14 | this.args = String.join(" \\\n && ", commands); 15 | } 16 | 17 | @Override 18 | public String execute(BuildContext context) { 19 | return "RUN " + args; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /jdock/src/main/java/io/quarkus/images/commands/RunExecCommand.java: -------------------------------------------------------------------------------- 1 | package io.quarkus.images.commands; 2 | 3 | import io.quarkus.images.BuildContext; 4 | 5 | import java.util.Arrays; 6 | import java.util.stream.Collectors; 7 | 8 | /** 9 | * Run command following the _exec_ form: 10 | * {@code RUN ["executable", "param1", "param2"]} 11 | */ 12 | public class RunExecCommand implements Command { 13 | 14 | private final String args; 15 | 16 | public RunExecCommand(String... commands) { 17 | this.args = "[ %s ]" 18 | .formatted(Arrays.stream(commands).map(s -> "\"" + s + "\"").collect(Collectors.joining(", "))); 19 | } 20 | 21 | @Override 22 | public String execute(BuildContext context) { 23 | return "RUN " + args; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /jdock/src/main/java/io/quarkus/images/commands/UserCommand.java: -------------------------------------------------------------------------------- 1 | package io.quarkus.images.commands; 2 | 3 | import io.quarkus.images.BuildContext; 4 | 5 | public class UserCommand implements Command { 6 | 7 | public static final String ROOT = "root"; 8 | 9 | private final String user; 10 | 11 | public UserCommand(String user) { 12 | this.user = user; 13 | 14 | } 15 | 16 | public static UserCommand root() { 17 | return new UserCommand(ROOT); 18 | } 19 | 20 | public static UserCommand user(String user) { 21 | return new UserCommand(user); 22 | } 23 | 24 | @Override 25 | public String execute(BuildContext context) { 26 | context.setCurrentUser(user); 27 | return "USER " + user; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /jdock/src/main/java/io/quarkus/images/commands/WorkDirCommand.java: -------------------------------------------------------------------------------- 1 | package io.quarkus.images.commands; 2 | 3 | import io.quarkus.images.BuildContext; 4 | 5 | public class WorkDirCommand implements Command { 6 | 7 | private final String dir; 8 | 9 | public WorkDirCommand(String dir) { 10 | this.dir = dir; 11 | 12 | } 13 | 14 | @Override 15 | public String execute(BuildContext context) { 16 | return "WORKDIR " + dir; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /jdock/src/main/java/io/quarkus/images/installers/FailingPackageManager.java: -------------------------------------------------------------------------------- 1 | package io.quarkus.images.installers; 2 | 3 | import io.quarkus.images.commands.Command; 4 | import io.quarkus.images.commands.NoopCommand; 5 | 6 | public class FailingPackageManager implements PackageManager { 7 | @Override 8 | public String name() { 9 | return "failing"; 10 | } 11 | 12 | @Override 13 | public Command cleanup() { 14 | return NoopCommand.INSTANCE; 15 | } 16 | 17 | @Override 18 | public Command install(String... packages) { 19 | throw new IllegalStateException("Unable to install a package, the package manager is not configured."); 20 | } 21 | 22 | @Override 23 | public boolean hasBeenUsed() { 24 | return false; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /jdock/src/main/java/io/quarkus/images/installers/MicroDnf.java: -------------------------------------------------------------------------------- 1 | package io.quarkus.images.installers; 2 | 3 | import io.quarkus.images.BuildContext; 4 | import io.quarkus.images.commands.Command; 5 | import io.quarkus.images.commands.MultiCommands; 6 | import io.quarkus.images.commands.RunCommand; 7 | import io.quarkus.images.commands.UserCommand; 8 | 9 | import java.util.ArrayList; 10 | import java.util.Collections; 11 | import java.util.List; 12 | 13 | public class MicroDnf implements PackageManager { 14 | 15 | public static final String TEMPLATE = """ 16 | microdnf --setopt=install_weak_deps=0 --setopt=tsflags=nodocs install -y %s \\ 17 | && rpm -q %s"""; 18 | 19 | private boolean used; 20 | 21 | @Override 22 | public String name() { 23 | return "microdnf"; 24 | } 25 | 26 | @Override 27 | public Command cleanup() { 28 | return new RunCommand("microdnf clean all && [ ! -d /var/cache/yum ] || rm -rf /var/cache/yum"); 29 | } 30 | 31 | @Override 32 | public Command install(String... packages) { 33 | used = true; 34 | String list = String.join(" ", packages); 35 | List commands = new ArrayList<>(); 36 | commands.add(new RunCommand(TEMPLATE.formatted(list, list))); 37 | 38 | return new MultiCommands() { 39 | @Override 40 | public List commands(BuildContext bc) { 41 | return commands; 42 | } 43 | 44 | private String originalUser = null; 45 | 46 | @Override 47 | public List before(BuildContext bc) { 48 | if (!bc.isCurrentUserRoot()) { 49 | originalUser = bc.getCurrentUser(); 50 | return List.of(UserCommand.root()); 51 | } 52 | return Collections.emptyList(); 53 | } 54 | 55 | @Override 56 | public List after(BuildContext bc) { 57 | if (originalUser != null) { 58 | return List.of(UserCommand.user(originalUser)); 59 | } 60 | return Collections.emptyList(); 61 | } 62 | }; 63 | } 64 | 65 | @Override 66 | public boolean hasBeenUsed() { 67 | return used; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /jdock/src/main/java/io/quarkus/images/installers/PackageManager.java: -------------------------------------------------------------------------------- 1 | package io.quarkus.images.installers; 2 | 3 | import io.quarkus.images.commands.Command; 4 | 5 | public interface PackageManager { 6 | String name(); 7 | 8 | Command cleanup(); 9 | 10 | Command install(String... packages); 11 | 12 | boolean hasBeenUsed(); 13 | } 14 | -------------------------------------------------------------------------------- /jdock/src/main/java/io/quarkus/images/installers/PackageManagers.java: -------------------------------------------------------------------------------- 1 | package io.quarkus.images.installers; 2 | 3 | public class PackageManagers { 4 | 5 | public static PackageManager guess(String from) { 6 | if (from == null) { 7 | return new FailingPackageManager(); 8 | } 9 | 10 | if (from.contains("ubi") && from.contains("minimal")) { 11 | return new MicroDnf(); 12 | } 13 | 14 | return new FailingPackageManager(); 15 | } 16 | 17 | public static PackageManager find(String name) { 18 | if (name == null) { 19 | return new FailingPackageManager(); 20 | } 21 | 22 | if (name.equalsIgnoreCase("microdnf")) { 23 | return new MicroDnf(); 24 | } 25 | 26 | throw new UnsupportedOperationException("Unknown package manager: " + name); 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /jdock/src/main/java/io/quarkus/images/modules/AbstractModule.java: -------------------------------------------------------------------------------- 1 | package io.quarkus.images.modules; 2 | 3 | import io.quarkus.images.commands.MultiCommands; 4 | 5 | public abstract class AbstractModule implements MultiCommands { 6 | 7 | public final String name; 8 | public final String version; 9 | 10 | public AbstractModule(String name) { 11 | this(name, "0.0.0"); 12 | } 13 | 14 | public AbstractModule(String name, String version) { 15 | this.name = name; 16 | this.version = version; 17 | } 18 | 19 | @Override 20 | public String toString() { 21 | if (version.equalsIgnoreCase("0.0.0")) { 22 | return name; 23 | } 24 | return name + " " + version; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /jdock/src/main/java/io/quarkus/images/modules/GraalVMModule.java: -------------------------------------------------------------------------------- 1 | package io.quarkus.images.modules; 2 | 3 | import io.quarkus.images.BuildContext; 4 | import io.quarkus.images.artifacts.Artifact; 5 | import io.quarkus.images.commands.*; 6 | 7 | import java.util.List; 8 | 9 | public class GraalVMModule extends AbstractModule { 10 | public static final String GRAALVM_HOME = "/opt/graalvm"; 11 | private final String url; 12 | private final String sha; 13 | private final String filename; 14 | 15 | /** 16 | * Indicates whether the graalvm version is using the old scheme (21.x, 22.x) ({@code true}), 17 | * or the new ones (17/20...) 18 | */ 19 | private final boolean isLegacyGraalVm; 20 | 21 | private static final String TEMPLATE = """ 22 | tar xzf %s -C /opt \\ 23 | && mv /opt/graalvm-ce-*-%s* /opt/graalvm \\ 24 | && %s/bin/gu --auto-yes install native-image \\ 25 | && rm -Rf %s"""; 26 | 27 | private static final String NEW_TEMPLATE = """ 28 | mkdir -p /opt/graalvm \\ 29 | && tar xzf %s -C /opt/graalvm --strip-components=1 \\ 30 | && rm -Rf %s"""; 31 | private final String graalvmVersion; 32 | 33 | public GraalVMModule(String version, String arch, String javaVersion, String sha) { 34 | super("graalvm", 35 | version == null ? "jdk-" + javaVersion + "-" + arch 36 | : arch != null ? version + "-java" + javaVersion + "-" + arch 37 | : version + "-java" + javaVersion + "-amd64"); 38 | 39 | isLegacyGraalVm = version != null; 40 | if (arch == null) { 41 | arch = "amd64"; 42 | } else if (arch.equalsIgnoreCase("arm64")) { 43 | arch = "aarch64"; 44 | } else if (version == null) { 45 | arch = "x64"; 46 | } 47 | 48 | // local file name: 49 | if (isLegacyGraalVm) { 50 | this.filename = "graalvm-java-%s-linux-%s-%s.tar.gz" 51 | .formatted(javaVersion, arch, version); 52 | } else { 53 | this.filename = "graalvm-jdk-%s-linux-%s.tar.gz" 54 | .formatted(javaVersion, arch); 55 | } 56 | 57 | if (isLegacyGraalVm) { 58 | this.url = "https://github.com/graalvm/graalvm-ce-builds/releases/download/vm-%s/graalvm-ce-java%s-linux-%s-%s.tar.gz" 59 | .formatted(version, javaVersion, arch, version); 60 | } else { 61 | this.url = "https://github.com/graalvm/graalvm-ce-builds/releases/download/jdk-%s/graalvm-community-jdk-%s_linux-%s_bin.tar.gz" 62 | .formatted(javaVersion, javaVersion, arch); 63 | } 64 | this.sha = sha; 65 | this.graalvmVersion = version == null ? javaVersion : version; 66 | } 67 | 68 | @Override 69 | public List commands(BuildContext bc) { 70 | Artifact artifact = bc.addArtifact(new Artifact(filename, url, sha)); 71 | String script; 72 | if (isLegacyGraalVm) { 73 | script = TEMPLATE.formatted( 74 | "/tmp/" + artifact.name, // tar 75 | graalvmVersion, 76 | GRAALVM_HOME, // gu 77 | "/tmp/" + artifact.name); // rm 78 | } else { 79 | script = NEW_TEMPLATE.formatted( 80 | "/tmp/" + artifact.name, // tar 81 | "/tmp/" + artifact.name); // rm 82 | } 83 | 84 | return List.of( 85 | new EnvCommand("JAVA_HOME", GRAALVM_HOME, "GRAALVM_HOME", GRAALVM_HOME), 86 | new MicrodnfCommand("fontconfig", "freetype-devel"), 87 | new CopyCommand(artifact, "/tmp/" + artifact.name), 88 | new RunCommand(script)); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /jdock/src/main/java/io/quarkus/images/modules/GradleModule.java: -------------------------------------------------------------------------------- 1 | package io.quarkus.images.modules; 2 | 3 | import io.quarkus.images.BuildContext; 4 | import io.quarkus.images.artifacts.Artifact; 5 | import io.quarkus.images.commands.Command; 6 | import io.quarkus.images.commands.CopyCommand; 7 | import io.quarkus.images.commands.EnvCommand; 8 | import io.quarkus.images.commands.LabelCommand; 9 | import io.quarkus.images.commands.RunCommand; 10 | 11 | import java.util.List; 12 | 13 | public class GradleModule extends AbstractModule { 14 | 15 | private static final String VERSION = "8.8"; 16 | private static final String SHA = "a4b4158601f8636cdeeab09bd76afb640030bb5b144aafe261a5e8af027dc612"; 17 | 18 | private static final String SCRIPT_INSTALL = """ 19 | unzip %s \\ 20 | && mv gradle-%s %s \\ 21 | && ln -s %s/bin/gradle /usr/bin/gradle"""; 22 | 23 | private static final String GRADLE_HOME = "/usr/share/gradle"; 24 | 25 | private final String url; 26 | 27 | public GradleModule() { 28 | super("gradle", VERSION); 29 | this.url = "https://services.gradle.org/distributions/gradle-%s-bin.zip".formatted(VERSION); 30 | } 31 | 32 | @Override 33 | public List commands(BuildContext bc) { 34 | Artifact gradle = bc.addArtifact(new Artifact("gradle.zip", url, SHA)); 35 | return List.of( 36 | new CopyCommand(gradle, "/tmp/" + gradle.name), 37 | new RunCommand(SCRIPT_INSTALL.formatted( 38 | "/tmp/" + gradle.name, // unzip 39 | version, GRADLE_HOME, // mv 40 | GRADLE_HOME // ln 41 | )), 42 | new EnvCommand("GRADLE_VERSION", version, "GRADLE_HOME", GRADLE_HOME, 43 | "GRADLE_OPTS", "-Dorg.gradle.daemon=false"), 44 | new LabelCommand("GRADLE_VERSION", VERSION)); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /jdock/src/main/java/io/quarkus/images/modules/MandrelModule.java: -------------------------------------------------------------------------------- 1 | package io.quarkus.images.modules; 2 | 3 | import io.quarkus.images.BuildContext; 4 | import io.quarkus.images.artifacts.Artifact; 5 | import io.quarkus.images.commands.Command; 6 | import io.quarkus.images.commands.CopyCommand; 7 | import io.quarkus.images.commands.EnvCommand; 8 | import io.quarkus.images.commands.MicrodnfCommand; 9 | import io.quarkus.images.commands.RunCommand; 10 | 11 | import java.io.IOException; 12 | import java.nio.file.Files; 13 | import java.nio.file.Path; 14 | import java.util.List; 15 | import java.util.concurrent.TimeUnit; 16 | import java.util.regex.Matcher; 17 | import java.util.regex.Pattern; 18 | 19 | public class MandrelModule extends AbstractModule { 20 | public static final String MANDREL_HOME = "/opt/mandrel"; 21 | private final String url; 22 | private final String sha; 23 | private final String filename; 24 | 25 | /* 26 | * e.g. 27 | * "21.0.7+6-LTS" -> 21, 0, 7, 6 28 | * "25-beta+20-ea" -> 25, 0, 0, 20 29 | */ 30 | public static final Pattern TEMURIN_RELEASE_PATTERN = Pattern.compile( 31 | "^(\\d+)(?:\\.(\\d+)\\.(\\d+))?(?:[^+]*\\+(\\d+))?.*$"); 32 | 33 | private static final String TEMPLATE = """ 34 | mkdir -p %s \\ 35 | && tar xzf %s -C %s --strip-components=1 \\ 36 | && rm -Rf %s"""; 37 | 38 | public MandrelModule(String version, String arch, String javaVersion, String sha) { 39 | super("mandrel", version + "-java" + javaVersion + "-" + arch); 40 | this.filename = "mandrel-java%s-linux-%s-%s.tar.gz" 41 | .formatted(javaVersion, arch, version); 42 | this.url = "https://github.com/graalvm/mandrel/releases/download/mandrel-%s/mandrel-java%s-linux-%s-%s.tar.gz" 43 | .formatted(version, javaVersion, arch, version); 44 | this.sha = sha; 45 | } 46 | 47 | public MandrelModule(String sha, String url) { 48 | super("mandrel", url.substring(url.lastIndexOf('/') + 1, url.lastIndexOf(".tar"))); 49 | this.filename = url.substring(url.lastIndexOf('/') + 1); 50 | this.url = url; 51 | this.sha = sha; 52 | } 53 | 54 | @Override 55 | public List commands(BuildContext bc) { 56 | Artifact artifact = bc.addArtifact(new Artifact(filename, url, sha)); 57 | String script = TEMPLATE.formatted( 58 | MANDREL_HOME, "/tmp/" + artifact.name, MANDREL_HOME, "/tmp/" + artifact.name); 59 | bc.setJdkVersion(getJDKVersion(artifact)); 60 | return List.of( 61 | new EnvCommand("JAVA_HOME", MANDREL_HOME, "GRAALVM_HOME", MANDREL_HOME), 62 | new MicrodnfCommand("fontconfig", "freetype-devel"), 63 | new CopyCommand(artifact, "/tmp/" + artifact.name), 64 | new RunCommand(script)); 65 | } 66 | 67 | public static int[] parseJDKVersion(String version) { 68 | final Matcher m = TEMURIN_RELEASE_PATTERN.matcher(version.replace("\"", "")); 69 | if (m.matches()) { 70 | final int feature = Integer.parseInt(m.group(1)); // Feature is always there... 71 | final int interim = (m.group(2) != null) ? Integer.parseInt(m.group(2)) : 0; 72 | final int update = (m.group(3) != null) ? Integer.parseInt(m.group(3)) : 0; 73 | final int build = (m.group(4) != null) ? Integer.parseInt(m.group(4)) : 0; 74 | return new int[] { feature, interim, update, build }; 75 | } 76 | throw new IllegalArgumentException("Unknown version format: " + version); 77 | } 78 | 79 | /** 80 | * Relies on a particular "release" file carried over from Temurin JDK to Mandrel distributions. 81 | * Other distributions don't have it. 82 | */ 83 | public static int[] getJDKVersion(Artifact a) { 84 | try { 85 | // Leaving the dir there, cleanup not necessary... 86 | final Path tempDir = Files.createTempDirectory("mandrel_version_check"); 87 | // Mandrel has the file, copied from Temurin. 88 | if(!a.store.exists()) { 89 | throw new RuntimeException("Artifact not found: " + a.store.getAbsolutePath()); 90 | } 91 | final ProcessBuilder pb = new ProcessBuilder( 92 | "tar", 93 | "--extract", 94 | "--wildcards", 95 | "--file", a.store.getAbsolutePath(), 96 | "--directory", tempDir.toString(), 97 | "--strip-components=1", 98 | "*/release"); 99 | pb.environment().put("PATH", System.getenv("PATH")); 100 | pb.inheritIO(); 101 | final Process p = pb.start(); 102 | p.waitFor(30, TimeUnit.SECONDS); 103 | final String jvmVersion = Files.readAllLines(tempDir.resolve("release")) // tiny file <2K 104 | .stream().filter(line -> line.startsWith("JVM_VERSION=")) 105 | .findFirst() 106 | .orElseThrow(() -> new RuntimeException("JVM_VERSION not found in release file")) 107 | .replace("JVM_VERSION=", "").replace("\"", "").trim(); 108 | System.out.println("JVM_VERSION: " + jvmVersion); 109 | return parseJDKVersion(jvmVersion); 110 | } catch (InterruptedException | IOException e) { 111 | throw new RuntimeException("Unable to extract JDK version from Mandrel distribution tarball.", e); 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /jdock/src/main/java/io/quarkus/images/modules/MavenModule.java: -------------------------------------------------------------------------------- 1 | package io.quarkus.images.modules; 2 | 3 | import io.quarkus.images.BuildContext; 4 | import io.quarkus.images.artifacts.Artifact; 5 | import io.quarkus.images.commands.Command; 6 | import io.quarkus.images.commands.CopyCommand; 7 | import io.quarkus.images.commands.EnvCommand; 8 | import io.quarkus.images.commands.LabelCommand; 9 | import io.quarkus.images.commands.RunCommand; 10 | 11 | import java.net.URL; 12 | import java.util.List; 13 | import java.util.NoSuchElementException; 14 | 15 | public class MavenModule extends AbstractModule { 16 | 17 | private static final String SCRIPT_INSTALL = """ 18 | tar xzf %s -C /usr/share \\ 19 | && mv /usr/share/apache-maven-%s %s \\ 20 | && ln -s %s/bin/mvn /usr/bin/mvn"""; 21 | 22 | private static final String SCRIPT_CONFIGURE = """ 23 | mkdir -p ${APP_HOME}/.m2/repository \\ 24 | && cp -v %s %s ${APP_HOME}/.m2/ \\ 25 | && ls ${APP_HOME}/.m2 \\ 26 | && chown -R 1001:0 ${APP_HOME} \\ 27 | && sh ${APP_HOME}/.m2/configure-maven.sh"""; 28 | 29 | private static final String MAVEN_HOME = "/usr/share/maven"; 30 | private static final String VERSION = "3.9.8"; 31 | private static final String SHA = "067672629075b740e3d0a928e21021dd615a53287af36d4ccca44e87e081d102"; 32 | 33 | private final String url; 34 | 35 | public MavenModule() { 36 | super("apache-maven", VERSION); 37 | this.url = "https://archive.apache.org/dist/maven/maven-3/%s/binaries/apache-maven-%s-bin.tar.gz".formatted(VERSION, 38 | VERSION); 39 | } 40 | 41 | @Override 42 | public List commands(BuildContext bc) { 43 | Artifact artifact = bc.addArtifact(new Artifact("apache-maven-" + VERSION + ".tar.gz", url, SHA)); 44 | Artifact settings = bc.addArtifact(new Artifact("maven-settings.xml", getUrl("settings.xml"), null)); 45 | Artifact configure_maven = bc.addArtifact(new Artifact("configure-maven.sh", getUrl("configure-maven.sh"), null)); 46 | return List.of( 47 | new CopyCommand(artifact, "/tmp/" + artifact.name), 48 | new CopyCommand(settings, "/tmp/" + settings.name), 49 | new CopyCommand(configure_maven, "/tmp/" + configure_maven.name), 50 | new RunCommand(SCRIPT_INSTALL.formatted( 51 | "/tmp/" + artifact.name, // tar 52 | version, MAVEN_HOME, // mv 53 | MAVEN_HOME // ln 54 | )), 55 | new RunCommand(SCRIPT_CONFIGURE.formatted( 56 | "/tmp/" + settings.name, "/tmp/" + configure_maven.name // cp 57 | )), 58 | new EnvCommand("MAVEN_VERSION", version, "MAVEN_HOME", MAVEN_HOME, 59 | "MAVEN_OPTS", "-XX:+TieredCompilation -XX:TieredStopAtLevel=1"), 60 | new LabelCommand("MAVEN_VERSION", VERSION)); 61 | } 62 | 63 | public String getUrl(String res) { 64 | URL resource = this.getClass().getClassLoader().getResource("maven/" + res); 65 | if (resource == null) { 66 | throw new NoSuchElementException("Cannot find maven/" + res + " in the classpath"); 67 | } 68 | return resource.toExternalForm(); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /jdock/src/main/java/io/quarkus/images/modules/QuarkusDirectoryModule.java: -------------------------------------------------------------------------------- 1 | package io.quarkus.images.modules; 2 | 3 | import io.quarkus.images.BuildContext; 4 | import io.quarkus.images.commands.Command; 5 | import io.quarkus.images.commands.RunCommand; 6 | import io.quarkus.images.commands.UserCommand; 7 | 8 | import java.util.Collections; 9 | import java.util.List; 10 | 11 | public class QuarkusDirectoryModule extends AbstractModule { 12 | public QuarkusDirectoryModule() { 13 | super("Quarkus directory"); 14 | } 15 | 16 | private String originalUser = null; 17 | 18 | @Override 19 | public List before(BuildContext bc) { 20 | if (!bc.isCurrentUserRoot()) { 21 | originalUser = bc.getCurrentUser(); 22 | return List.of(UserCommand.root()); 23 | } 24 | return Collections.emptyList(); 25 | } 26 | 27 | @Override 28 | public List after(BuildContext bc) { 29 | if (originalUser != null) { 30 | return List.of(UserCommand.user(originalUser)); 31 | } 32 | return Collections.emptyList(); 33 | } 34 | 35 | @Override 36 | public List commands(BuildContext bc) { 37 | return List.of( 38 | new RunCommand("mkdir /project"), 39 | new RunCommand("chown quarkus:quarkus /project") // Must be a separate command. 40 | ); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /jdock/src/main/java/io/quarkus/images/modules/QuarkusUserModule.java: -------------------------------------------------------------------------------- 1 | package io.quarkus.images.modules; 2 | 3 | import io.quarkus.images.BuildContext; 4 | import io.quarkus.images.commands.*; 5 | 6 | import java.util.Collections; 7 | import java.util.List; 8 | 9 | public class QuarkusUserModule extends AbstractModule implements MultiCommands { 10 | 11 | private String originalUser; 12 | 13 | public QuarkusUserModule() { 14 | super("quarkus-user"); 15 | } 16 | 17 | @Override 18 | public List before(BuildContext bc) { 19 | if (!bc.isCurrentUserRoot()) { 20 | originalUser = bc.getCurrentUser(); 21 | return List.of(UserCommand.root()); 22 | } 23 | return Collections.emptyList(); 24 | } 25 | 26 | @Override 27 | public List after(BuildContext bc) { 28 | if (originalUser != null) { 29 | return List.of(UserCommand.user(originalUser)); 30 | } 31 | return Collections.emptyList(); 32 | } 33 | 34 | @Override 35 | public List commands(BuildContext context) { 36 | return List.of( 37 | new EnvCommand("APP_HOME", "/home/quarkus"), 38 | new RunCommand( 39 | "groupadd -r quarkus -g 1001 && useradd -u 1001 -r -g 1001 -m -d ${APP_HOME} -s /sbin/nologin -c \"Quarkus user\" quarkus")); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /jdock/src/main/java/io/quarkus/images/modules/UpxModule.java: -------------------------------------------------------------------------------- 1 | package io.quarkus.images.modules; 2 | 3 | import io.quarkus.images.BuildContext; 4 | import io.quarkus.images.artifacts.Artifact; 5 | import io.quarkus.images.commands.*; 6 | 7 | import java.util.List; 8 | import java.util.Objects; 9 | 10 | public class UpxModule extends AbstractModule { 11 | 12 | public static final String UPX_VERSION = "3.96"; 13 | public static final String URL = "https://github.com/upx/upx/releases/download/v%s/upx-%s-%s_linux.tar.xz"; 14 | private final String arch; 15 | 16 | public UpxModule(String arch) { 17 | super("upx"); 18 | this.arch = Objects.requireNonNullElse(arch, "amd64"); 19 | } 20 | 21 | @Override 22 | public List commands(BuildContext bc) { 23 | String archive = "upx-" + arch + ".xz"; 24 | Artifact artifact = bc.addArtifact(new Artifact(archive, URL.formatted(UPX_VERSION, UPX_VERSION, arch), null)); 25 | return List.of( 26 | new PackageCommand("xz"), 27 | new EnvCommand("UPX_VERSION", UPX_VERSION), 28 | new CopyCommand(artifact, "/tmp/" + artifact.name), 29 | new RunCommand("tar xf /tmp/" + artifact.name + " -C /tmp", 30 | "cd /tmp/upx-" + UPX_VERSION + "-" + arch + "_linux", 31 | "mv upx /usr/bin/upx", "rm -Rf /tmp/" + artifact.name)); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /jdock/src/main/java/io/quarkus/images/modules/UsLangModule.java: -------------------------------------------------------------------------------- 1 | package io.quarkus.images.modules; 2 | 3 | import io.quarkus.images.BuildContext; 4 | import io.quarkus.images.commands.Command; 5 | import io.quarkus.images.commands.EnvCommand; 6 | 7 | import java.util.List; 8 | 9 | public class UsLangModule extends AbstractModule { 10 | public UsLangModule() { 11 | super("en_US encoding"); 12 | } 13 | 14 | @Override 15 | public List commands(BuildContext bc) { 16 | return List.of(new EnvCommand("LANG", "en_US.UTF-8", "LANGUAGE", "en_US:en", "LC_ALL", "en_US.UTF-8")); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /jdock/src/main/java/io/quarkus/images/utils/Commands.java: -------------------------------------------------------------------------------- 1 | package io.quarkus.images.utils; 2 | 3 | import io.quarkus.images.commands.Command; 4 | import io.quarkus.images.commands.UserCommand; 5 | 6 | import java.util.List; 7 | 8 | public class Commands { 9 | 10 | public static int lastUserCommand(List commands) { 11 | int pos = -1; 12 | for (int i = 0; i < commands.size(); i++) { 13 | if (commands.get(i) instanceof UserCommand) { 14 | pos = i; 15 | } 16 | } 17 | return pos; 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /jdock/src/main/java/io/quarkus/images/utils/Exec.java: -------------------------------------------------------------------------------- 1 | package io.quarkus.images.utils; 2 | 3 | import io.quarkus.images.Buildable; 4 | import io.quarkus.images.JDock; 5 | import org.zeroturnaround.exec.ProcessExecutor; 6 | import org.zeroturnaround.exec.stream.LogOutputStream; 7 | 8 | import java.io.File; 9 | import java.nio.file.Paths; 10 | import java.util.ArrayList; 11 | import java.util.Arrays; 12 | import java.util.List; 13 | import java.util.function.Function; 14 | import java.util.regex.Pattern; 15 | import java.util.stream.Stream; 16 | 17 | public class Exec { 18 | public static void execute(List command, Function exceptionMapper) { 19 | try { 20 | new ProcessExecutor().command(command) 21 | .redirectOutput(new LogOutputStream() { 22 | @Override 23 | protected void processLine(String s) { 24 | System.out.println("\uD83D\uDC0B\t" + s); 25 | } 26 | }) 27 | .exitValue(0) 28 | .execute(); 29 | } catch (Exception e) { 30 | throw exceptionMapper.apply(e); 31 | } 32 | } 33 | 34 | public static void buildLocal(Buildable df, String name, String arch, boolean dryRun) { 35 | build(df, name, arch, true, dryRun); 36 | } 37 | 38 | private static void build(Buildable df, String name, String arch, boolean local, boolean dryRun) { 39 | String imageName = name; 40 | if (arch != null) { 41 | imageName = name + "-" + arch; 42 | } 43 | 44 | if (local) { 45 | System.out.println("⚙️\tBuilding single-arch image: " + imageName); 46 | } else { 47 | System.out.println("⚙️\tBuilding single-arch image and push it: " + imageName); 48 | } 49 | 50 | String fileName = imageName.toLowerCase() + ".Dockerfile"; 51 | while (fileName.contains("/")) { 52 | fileName = fileName.substring(fileName.indexOf("/") + 1); 53 | } 54 | File dockerfile = new File(JDock.dockerFileDir + "/" + fileName); 55 | df.build(dockerfile); 56 | if (!dockerfile.isFile()) { 57 | throw new IllegalStateException("File " + dockerfile.getAbsolutePath() + " does not exist"); 58 | } 59 | System.out.println("⚙️\tDockerfile created in: " + dockerfile.getAbsolutePath()); 60 | 61 | if (!dryRun) { 62 | System.out.println("⚙️\tLaunching the build process: "); 63 | List list = new ArrayList<>( 64 | Arrays.asList("docker", "buildx", "build", "--load", "-f", JDock.dockerFileDir + "/" + fileName)); 65 | if (arch != null) { 66 | list.add("--platform=linux/" + arch); 67 | } 68 | list.add("--tag"); 69 | list.add(imageName); 70 | list.add("."); 71 | Exec.execute(list, 72 | e -> new RuntimeException("Unable to build image for " + dockerfile.getAbsolutePath(), e)); 73 | System.out.println("⚙️\tImage " + imageName + " created"); 74 | } else { 75 | System.out.println("⚠️️\tSkipping the container build for " + imageName 76 | + " (dry-run), the Dockerfile has been generated in " + dockerfile.getAbsolutePath()); 77 | } 78 | 79 | if (!local && !dryRun) { 80 | String t = imageName; 81 | Exec.execute(List.of("docker", "push", imageName), 82 | e -> new RuntimeException("Unable to push image " + t, e)); 83 | System.out.println("⚙️\tImage " + imageName + " pushed"); 84 | } else if (!local) { 85 | System.out.println("⚙️\tSkipping docker push (dry-run)"); 86 | } 87 | 88 | } 89 | 90 | public static String getContainerTool() { 91 | if (findExecutable("docker") != null) { 92 | return "docker"; 93 | } 94 | if (findExecutable("podman") != null) { 95 | return "podman"; 96 | } 97 | throw new IllegalStateException("No container tool found in system path"); 98 | } 99 | 100 | public static void buildAndPush(Buildable df, String name, String arch) { 101 | build(df, name, arch, false, false); // no dry run when pushing images. 102 | } 103 | 104 | public static String findExecutable(String exec) { 105 | return Stream.of(System.getenv("PATH").split(Pattern.quote(File.pathSeparator))).map(Paths::get) 106 | .map(path -> path.resolve(exec).toFile()).filter(File::exists).findFirst().map(File::getParent) 107 | .orElse(null); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /jdock/src/main/resources/maven/configure-maven.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | function prepareEnv() { 4 | unset HTTPS_PROXY 5 | unset HTTP_PROXY_HOST 6 | unset HTTP_PROXY_PORT 7 | unset HTTP_PROXY_PASSWORD 8 | unset HTTP_PROXY_USERNAME 9 | unset HTTP_PROXY_NONPROXYHOSTS 10 | unset MAVEN_MIRROR_URL 11 | } 12 | 13 | function configure() { 14 | configure_proxy 15 | configure_mirrors 16 | } 17 | 18 | # insert settings for HTTP proxy into maven settings.xml if supplied 19 | function configure_proxy() { 20 | 21 | # prefer old http_proxy_ format for username/password, but 22 | # also allow proxy_ format. 23 | HTTP_PROXY_USERNAME=${HTTP_PROXY_USERNAME:-$PROXY_USERNAME} 24 | HTTP_PROXY_PASSWORD=${HTTP_PROXY_PASSWORD:-$PROXY_PASSWORD} 25 | 26 | proxy=${HTTPS_PROXY:-${https_proxy:-${HTTP_PROXY:-$http_proxy}}} 27 | # if http_proxy_host/port is set, prefer that (oldest mechanism) 28 | # before looking at HTTP(S)_PROXY 29 | proxyhost=${HTTP_PROXY_HOST:-$(echo $proxy | cut -d / -f 3 | cut -d : -f 1)} 30 | proxyport=${HTTP_PROXY_PORT:-$(echo $proxy | cut -d : -f 3)} 31 | 32 | if [ -n "$proxyhost" ]; then 33 | if [[ `echo $proxyhost | grep -i https://` ]]; then 34 | proxyport=${proxyport:-443} 35 | proxyprotocol="https" 36 | else 37 | proxyport=${proxyport:-80} 38 | proxyprotocol="http" 39 | fi 40 | 41 | xml="\ 42 | genproxy\ 43 | true\ 44 | $proxyprotocol\ 45 | $proxyhost\ 46 | $proxyport" 47 | if [ -n "$HTTP_PROXY_USERNAME" -a -n "$HTTP_PROXY_PASSWORD" ]; then 48 | xml="$xml\ 49 | $HTTP_PROXY_USERNAME\ 50 | $HTTP_PROXY_PASSWORD" 51 | fi 52 | if [ -n "$HTTP_PROXY_NONPROXYHOSTS" ]; then 53 | xml="$xml\ 54 | $HTTP_PROXY_NONPROXYHOSTS" 55 | fi 56 | xml="$xml\ 57 | " 58 | sed -i "s||$xml|" $HOME/.m2/settings.xml 59 | fi 60 | } 61 | 62 | # insert settings for mirrors/repository managers into settings.xml if supplied 63 | function configure_mirrors() { 64 | if [ -n "$MAVEN_MIRROR_URL" ]; then 65 | xml=" \ 66 | mirror.default\ 67 | $MAVEN_MIRROR_URL\ 68 | external:*\ 69 | " 70 | sed -i "s||$xml|" $HOME/.m2/settings.xml 71 | fi 72 | } -------------------------------------------------------------------------------- /jdock/src/main/resources/maven/configure.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # This script executes a list of modules defined by CONFIGURE_SCRIPTS. 3 | # 4 | # Configuration occurs over three basic phases: preConfigure, configure and 5 | # postConfigure. 6 | # 7 | # The configure phase is special in that it iterates over a series of 8 | # environments. In addition to the execution environment, users may specify 9 | # additional configuration details through environment scripts specified through 10 | # the ENV_FILES variable. The processing of env files is similar to the process 11 | # for the execution environment, with the addition of a prepareEnv method, which 12 | # is used to clean the environment before processing the env contributed by 13 | # a file. This is to help ensure that duplicate configurations are not created 14 | # when processing env files. 15 | # 16 | # The following details the API which the modules may implement. If a 17 | # particular function is not implemented by a module, it is treated as a no-op. 18 | # 19 | # preConfigure: invoked before any configuration takes place. Use this to 20 | # manipulate the environment prior to configuration. For 21 | # example, the backward-compatiblity.sh module initializes 22 | # environment variables from older variable names, if present. 23 | # 24 | # configure: invoked to configure based on the execution environment. 25 | # 26 | # postConfigure: invoked after all configuration has been completed. 27 | # 28 | # prepareEnv: invoked prior to processing env files. Modules should 29 | # use this to prepare the environment before processing 30 | # configuration from a file, e.g. by unset variables 31 | # defined in the execution environment to prevent duplicate 32 | # configuration entries. This method is invoked once, as 33 | # each env file is processed in a subshell, thus preventing 34 | # contamination of the environment from file to file. 35 | # 36 | # preConfigureEnv: similar to preConfigure, except this is invoked after an env 37 | # file has been sourced, but before configureEnv. 38 | # 39 | # configureEnv: similar to configure. 40 | # 41 | # postConfigureEnv: simliar to postConfigure, except that it is invoked for each 42 | # env file. 43 | # 44 | # The reason the configuration API is duplicated for an Env, is that some 45 | # modules may not support env files, or may require configuration of "singleton" 46 | # type entries, which should only be processed once. 47 | # 48 | 49 | # clear functions from any previous module 50 | function prepareModule() { 51 | unset -f preConfigure 52 | unset -f configure 53 | unset -f postConfigure 54 | 55 | unset -f prepareEnv 56 | unset -f preConfigureEnv 57 | unset -f configureEnv 58 | unset -f postConfigureEnv 59 | } 60 | 61 | # Execute a particular function from a module 62 | # $1 - module file 63 | # $2 - function name 64 | function executeModule() { 65 | source $1; 66 | if [ -n "$(type -t $2)" ]; then 67 | eval $2 68 | fi 69 | } 70 | 71 | # Run through the list of scripts, executing the specified function for each. 72 | # $1 - function name 73 | function executeModules() { 74 | for module in ${CONFIGURE_SCRIPTS[@]}; do 75 | prepareModule 76 | executeModule $module $1 77 | done 78 | } 79 | 80 | # Processes the files provided by ENV_FILES. Invokes the *Env functions for 81 | # each module. Env processing is done in subshells. The outer subshell 82 | # provides a sanitized environment, that will be used by each inner subshell. 83 | # This insulates the execution environment from any changes made during env 84 | # file processing, and keeps the base environment the same from file to file 85 | # (i.e. we don't have to run prepareEnv for each file). 86 | function processEnvFiles() { 87 | if [ -n "$ENV_FILES" ]; then 88 | ( 89 | executeModules prepareEnv 90 | for prop_file_arg in $(echo $ENV_FILES | sed "s/,/ /g"); do 91 | for prop_file in $(find $prop_file_arg -maxdepth 0 2>/dev/null); do 92 | ( 93 | if [ -f $prop_file ]; then 94 | source $prop_file 95 | executeModules preConfigureEnv 96 | executeModules configureEnv 97 | executeModules postConfigureEnv 98 | else 99 | echo "Warning - Could not process environment for $prop_file. File does not exist." 100 | fi 101 | ) 102 | done 103 | done 104 | ) 105 | fi 106 | } 107 | 108 | executeModules preConfigure 109 | executeModules configure 110 | processEnvFiles 111 | executeModules postConfigure -------------------------------------------------------------------------------- /jdock/src/main/resources/maven/settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | jboss-eap-repository 13 | 14 | 15 | com.redhat.xpaas.repo.redhatga 16 | 17 | 18 | 19 | 20 | 21 | jboss-eap-repository 22 | https://maven.repository.redhat.com/techpreview/all 23 | 24 | true 25 | 26 | 27 | false 28 | 29 | 30 | 31 | 32 | 33 | jboss-eap-plugin-repository 34 | https://maven.repository.redhat.com/techpreview/all 35 | 36 | true 37 | 38 | 39 | false 40 | 41 | 42 | 43 | 44 | 45 | 46 | jboss-eap-repository-insecure 47 | 48 | 49 | jboss-eap-repository 50 | http://maven.repository.redhat.com/techpreview/all 51 | 52 | true 53 | 54 | 55 | false 56 | 57 | 58 | 59 | 60 | 61 | jboss-eap-plugin-repository 62 | http://maven.repository.redhat.com/techpreview/all 63 | 64 | true 65 | 66 | 67 | false 68 | 69 | 70 | 71 | 72 | 73 | 74 | jboss-community-repository 75 | 76 | 77 | com.redhat.xpaas.repo.jbossorg 78 | 79 | 80 | 81 | 82 | 83 | jboss-community-repository 84 | https://repository.jboss.org/nexus/content/groups/public/ 85 | 86 | true 87 | 88 | 89 | false 90 | 91 | 92 | 93 | 94 | 95 | jboss-community-plugin-repository 96 | https://repository.jboss.org/nexus/content/groups/public/ 97 | 98 | true 99 | 100 | 101 | false 102 | 103 | 104 | 105 | 106 | 107 | 108 | jboss-community-repository-insecure 109 | 110 | 111 | jboss-community-repository 112 | http://repository.jboss.org/nexus/content/groups/public/ 113 | 114 | true 115 | 116 | 117 | false 118 | 119 | 120 | 121 | 122 | 123 | jboss-community-plugin-repository 124 | http://repository.jboss.org/nexus/content/groups/public/ 125 | 126 | true 127 | 128 | 129 | false 130 | 131 | 132 | 133 | 134 | 135 | 136 | securecentral 137 | 138 | 139 | central 140 | https://repo1.maven.org/maven2 141 | 142 | true 143 | 144 | 145 | 146 | 147 | 148 | central 149 | https://repo1.maven.org/maven2 150 | 151 | true 152 | 153 | 154 | 155 | 156 | 157 | 158 | insecurecentral 159 | 160 | 161 | central 162 | http://repo1.maven.org/maven2 163 | 164 | true 165 | 166 | 167 | 168 | 169 | 170 | central 171 | http://repo1.maven.org/maven2 172 | 173 | true 174 | 175 | 176 | 177 | 178 | 179 | 180 | securecentral 181 | 182 | -------------------------------------------------------------------------------- /jdock/src/test/java/io/quarkus/images/Builders.java: -------------------------------------------------------------------------------- 1 | package io.quarkus.images; 2 | 3 | import io.quarkus.images.modules.*; 4 | 5 | import java.io.IOException; 6 | 7 | public class Builders { 8 | 9 | public static Dockerfile getMandrelDockerFile(String version, String javaVersion, String arch, String sha) { 10 | Dockerfile df = Dockerfile.from("registry.access.redhat.com/ubi8/ubi-minimal:8.10"); 11 | df 12 | .installer("microdnf") 13 | .user("root") 14 | .install("tar", "gzip", "gcc", "glibc-devel", "zlib-devel", "shadow-utils", "unzip", "gcc-c++") 15 | .install("glibc-langpack-en") 16 | .module(new UsLangModule()) 17 | .module(new QuarkusUserModule()) 18 | .module(new QuarkusDirectoryModule()) 19 | .module(new UpxModule(arch)) 20 | .module(pickMandrelModule(version, arch, javaVersion, sha)) 21 | .env("PATH", "$PATH:$JAVA_HOME/bin") 22 | .label("io.k8s.description", "Quarkus.io executable image providing the `native-image` executable.", 23 | "io.k8s.display-name", "Quarkus.io executable (GraalVM Native, Mandrel distribution)", 24 | "io.openshift.tags", "executable,java,quarkus,mandrel,native", 25 | "maintainer", "Quarkus Team ") 26 | .user("1001") 27 | .workdir("/project") 28 | .entrypoint("native-image"); 29 | 30 | return df; 31 | } 32 | 33 | /** 34 | * @see io.quarkus.images.QuarkusMandrelBuilder#pickMandrelModule(String, String, String, String) 35 | */ 36 | private static MandrelModule pickMandrelModule(String version, String arch, String javaVersion, String sha) { 37 | if (arch == null) { 38 | arch = "amd64"; 39 | } else if (arch.equalsIgnoreCase("arm64")) { 40 | arch = "aarch64"; 41 | } 42 | return new MandrelModule(version, arch, javaVersion, sha); 43 | } 44 | 45 | public static Dockerfile getGraalVmDockerFile(String version, String javaVersion, String arch, String sha) { 46 | Dockerfile df = Dockerfile.from("registry.access.redhat.com/ubi8/ubi-minimal:8.10"); 47 | df 48 | .installer("microdnf") 49 | .user("root") 50 | .install("tar", "gzip", "gcc", "glibc-devel", "zlib-devel", "shadow-utils", "unzip", "gcc-c++") 51 | .install("glibc-langpack-en") 52 | .module(new UsLangModule()) 53 | .module(new QuarkusUserModule()) 54 | .module(new QuarkusDirectoryModule()) 55 | .module(new UpxModule(arch)) 56 | .module(new GraalVMModule(version, arch, javaVersion, sha)) 57 | .env("PATH", "$PATH:$JAVA_HOME/bin") 58 | .label("io.k8s.description", "Quarkus.io executable image providing the `native-image` executable.", 59 | "io.k8s.display-name", "Quarkus.io executable (GraalVM Native)", 60 | "io.openshift.tags", "executable,java,quarkus,graalvm,native", 61 | "maintainer", "Quarkus Team ") 62 | .user("1001") 63 | .workdir("/project") 64 | .entrypoint("native-image"); 65 | 66 | return df; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /jdock/src/test/java/io/quarkus/images/DistrolessTest.java: -------------------------------------------------------------------------------- 1 | package io.quarkus.images; 2 | 3 | import org.junit.jupiter.api.BeforeAll; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import java.io.File; 7 | 8 | public class DistrolessTest { 9 | 10 | @BeforeAll 11 | static void init() { 12 | JDock.setDockerFileDir(new File("target/test")); 13 | } 14 | 15 | @Test 16 | void testBuildingDistrolessImage() { 17 | System.out.println(Dockerfile 18 | .multistages() 19 | .stage("debian", Dockerfile.from("debian-stage-slim")) 20 | .stage("scratch", Dockerfile.from("gcr.io/distroless/cc")) 21 | .stage(Dockerfile.from("scratch") 22 | .copyFromStage("debian", "/lib/x86_64-linux-gnu/libz.so.1", "/lib/x86_64-linux-gnu")) 23 | .build()); 24 | 25 | } 26 | 27 | @Test 28 | void testBuildingMicroImage() { 29 | System.out.println(Dockerfile 30 | .multistages() 31 | .stage("ubi", Dockerfile.from("registry.access.redhat.com/ubi8/ubi-minimal:8.10")) 32 | .stage("scratch", Dockerfile.from("registry.access.redhat.com/ubi8/ubi-micro")) 33 | .stage(Dockerfile.from("scratch") 34 | .copyFromStage("ubi", "/usr/lib64/libgcc_s.so.1") 35 | .copyFromStage("ubi", "/usr/lib64/libstdc++.so.6") 36 | .copyFromStage("ubi", "/usr/lib64/libz.so.1")) 37 | .build()); 38 | 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /jdock/src/test/java/io/quarkus/images/MandrelMultiArchTest.java: -------------------------------------------------------------------------------- 1 | package io.quarkus.images; 2 | 3 | import org.junit.jupiter.api.BeforeAll; 4 | import org.junit.jupiter.api.Disabled; 5 | import org.junit.jupiter.api.Test; 6 | 7 | import java.io.File; 8 | import java.util.Map; 9 | 10 | @Disabled 11 | public class MandrelMultiArchTest { 12 | 13 | @BeforeAll 14 | static void init() { 15 | JDock.setDockerFileDir(new File("target/test")); 16 | } 17 | 18 | @Test 19 | void test() { 20 | MultiArchImage multi = new MultiArchImage("cescoffier/mandrel-java17:22.1.0.0", Map.of( 21 | "amd64", getAmd64(), 22 | "arm64", getArm64())); 23 | 24 | multi.buildAndPush(); 25 | } 26 | 27 | public Dockerfile getAmd64() { 28 | String arch = "amd64"; 29 | String mandrel_version = "22.1.0.0-Final"; 30 | String sha = "b40bf617fd957fcb7fe61acc2621d0a84931822498b83b968f704b47e1e2edaf"; 31 | String java_version = "17"; 32 | return Builders.getMandrelDockerFile(mandrel_version, java_version, arch, sha); 33 | } 34 | 35 | public Dockerfile getArm64() { 36 | String arch = "arm64"; 37 | String mandrel_version = "22.1.0.0-Final"; 38 | String sha = "d6c7304b3ad6a3ca17664be092a5420905b2190407b45b40ef4312f723b38208"; 39 | String java_version = "17"; 40 | return Builders.getMandrelDockerFile(mandrel_version, java_version, arch, sha); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /jdock/src/test/java/io/quarkus/images/MavenAndGradleTest.java: -------------------------------------------------------------------------------- 1 | package io.quarkus.images; 2 | 3 | import io.quarkus.images.modules.GradleModule; 4 | import io.quarkus.images.modules.MavenModule; 5 | import io.quarkus.images.modules.QuarkusUserModule; 6 | import io.quarkus.images.modules.UsLangModule; 7 | import org.junit.jupiter.api.Test; 8 | 9 | public class MavenAndGradleTest { 10 | 11 | @Test 12 | void verifyMavenAndGradleInstallation() { 13 | Dockerfile cmd = Dockerfile.from("registry.access.redhat.com/ubi8/ubi-minimal:8.10") 14 | .user("root") 15 | .install("tar", "gzip", "gcc", "glibc-devel", "zlib-devel", "shadow-utils", "unzip", "gcc-c++", "tzdata") 16 | .install("glibc-langpack-en") 17 | .module(new UsLangModule()) 18 | .module(new QuarkusUserModule()) 19 | .module(new MavenModule()) 20 | .module(new GradleModule()) 21 | .env("PATH", "$PATH:$JAVA_HOME/bin") 22 | .user("1001") 23 | .workdir("${APP_HOME}") 24 | .expose(8080) 25 | .cmd("/usr/libexec/s2i/run"); 26 | cmd.build(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /jdock/src/test/java/io/quarkus/images/MultiMicroTest.java: -------------------------------------------------------------------------------- 1 | package io.quarkus.images; 2 | 3 | import org.junit.jupiter.api.BeforeAll; 4 | import org.junit.jupiter.api.Disabled; 5 | import org.junit.jupiter.api.Test; 6 | 7 | import java.io.File; 8 | import java.util.Map; 9 | 10 | @Disabled 11 | public class MultiMicroTest { 12 | 13 | @BeforeAll 14 | static void init() { 15 | JDock.setDockerFileDir(new File("target/test")); 16 | } 17 | 18 | @Test 19 | void test() { 20 | MultiStageDockerFile micro = Dockerfile.multistages() 21 | .stage("ubi", Dockerfile.from("registry.access.redhat.com/ubi8/ubi-minimal:8.10")) 22 | .stage("scratch", Dockerfile.from("registry.access.redhat.com/ubi8/ubi-micro")) 23 | .stage(Dockerfile.from("scratch") 24 | .copyFromStage("ubi", "/usr/lib64/libgcc_s.so.1") 25 | .copyFromStage("ubi", "/usr/lib64/libstdc++.so.6") 26 | .copyFromStage("ubi", "/usr/lib64/libz.so.1")); 27 | 28 | new MultiArchImage("cescoffier/quarkus-micro:1.0", Map.of( 29 | "arm64", micro, 30 | "amd64", micro)).buildAndPush(); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /jdock/src/test/java/io/quarkus/images/SimpleTest.java: -------------------------------------------------------------------------------- 1 | package io.quarkus.images; 2 | 3 | import org.junit.jupiter.api.BeforeAll; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import java.io.File; 7 | 8 | import static io.quarkus.images.modules.MandrelModule.parseJDKVersion; 9 | import static org.assertj.core.api.Assertions.assertThat; 10 | import static org.junit.jupiter.api.Assertions.assertArrayEquals; 11 | 12 | class SimpleTest { 13 | 14 | @BeforeAll 15 | static void init() { 16 | JDock.setDockerFileDir(new File("target/test")); 17 | } 18 | 19 | @Test 20 | public void testMandrelAmd64() { 21 | String arch = "amd64"; 22 | String mandrel_version = "22.3.0.1-Final"; 23 | String sha = "72ba94f4ca8e48eaa905433a6d0cfff5e7a657fb4f5419e86d7b8f5332ed0345"; 24 | String java_version = "17"; 25 | 26 | String filename = "mandrel-java%s-%s-%s.Dockerfile".formatted( 27 | java_version, mandrel_version, arch); 28 | Dockerfile dockerFile = Builders.getMandrelDockerFile(mandrel_version, java_version, arch, sha); 29 | dockerFile.build(new File("target/test/" + filename)); 30 | 31 | } 32 | 33 | @Test 34 | public void testMandrelArm64() { 35 | String arch = "arm64"; 36 | String mandrel_version = "22.3.0.1-Final"; 37 | String sha = "729ad2496191d4e0bc0dea3d19ac3ede0f4561e0ba4c3468d5824ca5a160d81b"; 38 | String java_version = "17"; 39 | 40 | String filename = "mandrel-java%s-%s-%s.Dockerfile".formatted( 41 | java_version, mandrel_version, arch); 42 | 43 | Dockerfile dockerFile = Builders.getMandrelDockerFile(mandrel_version, java_version, arch, sha); 44 | dockerFile.build(new File("target/test/" + filename)); 45 | } 46 | 47 | @Test 48 | public void testGraalvmAmd64() { 49 | String arch = "amd64"; 50 | String graalvm_version = "22.1.0"; 51 | String sha = "f11d46098efbf78465a875c502028767e3de410a31e45d92a9c5cf5046f42aa2"; 52 | String java_version = "17"; 53 | 54 | String filename = "graalvm-ce-java%s-%s-%s.Dockerfile".formatted( 55 | java_version, graalvm_version, arch); 56 | 57 | Dockerfile dockerFile = Builders.getGraalVmDockerFile(graalvm_version, java_version, arch, sha); 58 | dockerFile.build(new File("target/test/" + filename)); 59 | } 60 | 61 | @Test 62 | public void testRunWithExecForm() { 63 | Dockerfile df = Dockerfile.from("registry.access.redhat.com/ubi8/ubi-minimal:8.10"); 64 | df.exec("/bin/bash", "-c", "echo hello"); 65 | assertThat(df.build()).contains("RUN [ \"/bin/bash\", \"-c\", \"echo hello\" ]\n"); 66 | } 67 | 68 | @Test 69 | public void testRunWithShellForm() { 70 | Dockerfile df = Dockerfile.from("registry.access.redhat.com/ubi8/ubi-minimal:8.10"); 71 | df.run("source $HOME/.bashrc", "echo $HOME"); 72 | assertThat(df.build()).contains("RUN source $HOME/.bashrc \\\n && echo $HOME\n"); 73 | } 74 | 75 | @Test 76 | void testJDKVersionParsing() { 77 | assertArrayEquals(new int[] { 20, 0, 1, 9 }, parseJDKVersion("20.0.1+9")); 78 | assertArrayEquals(new int[] { 21, 1, 3, 9 }, parseJDKVersion("21.1.3+9-LTS")); 79 | assertArrayEquals(new int[] { 21, 0, 4, 7 }, parseJDKVersion("21.0.4+7-LTS")); 80 | assertArrayEquals(new int[] { 21, 0, 5, 4 }, parseJDKVersion("21.0.5-beta+4-ea")); 81 | assertArrayEquals(new int[] { 22, 0, 0, 36 }, parseJDKVersion("22+36")); 82 | assertArrayEquals(new int[] { 25, 0, 0, 20 }, parseJDKVersion("25-beta+20-ea")); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | 7 | io.smallrye 8 | smallrye-parent 9 | 35 10 | 11 | 12 | io.quarkus.images 13 | quarkus-images-parent 14 | 1.0-SNAPSHOT 15 | 16 | pom 17 | Quarkus images 18 | 19 | jdock 20 | jdock-variant-helper 21 | 22 | quarkus-micro-base-image 23 | quarkus-distroless-base-image 24 | quarkus-mandrel-builder-image 25 | quarkus-graalvm-builder-image 26 | 27 | quarkus-binary-s2i 28 | quarkus-native-s2i 29 | 30 | 31 | 32 | UTF-8 33 | 34 | 17 35 | 17 36 | 37 | registry.access.redhat.com/ubi8/ubi-minimal:8.10 38 | registry.access.redhat.com/ubi8-micro:8.10 39 | 40 | registry.access.redhat.com/ubi9/ubi-minimal:9.5 41 | registry.access.redhat.com/ubi9-micro:9.5 42 | 43 | false 44 | 45 | 46 | 47 | Clement Escoffier 48 | clement@apache.org 49 | 50 | Plumber 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | dev.jbang 60 | jbang-maven-plugin 61 | 0.0.7 62 | 63 | 64 | 65 | 66 | 67 | org.apache.maven.plugins 68 | maven-compiler-plugin 69 | 3.10.1 70 | 71 | 72 | org.apache.maven.plugins 73 | maven-jar-plugin 74 | 3.2.2 75 | 76 | 77 | 78 | net.revelc.code 79 | impsort-maven-plugin 80 | 1.7.0 81 | 82 | 83 | true 84 | 85 | 86 | 87 | 88 | org.apache.maven.plugins 89 | maven-deploy-plugin 90 | 2.8.2 91 | 92 | true 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | io.quarkus.images 102 | jdock 103 | ${project.version} 104 | 105 | 106 | io.quarkus.images 107 | jdock-variant-helper 108 | ${project.version} 109 | 110 | 111 | info.picocli 112 | picocli 113 | 4.7.4 114 | 115 | 116 | com.fasterxml.jackson.dataformat 117 | jackson-dataformat-yaml 118 | 2.15.2 119 | 120 | 121 | 122 | 123 | 124 | 125 | com.google.guava 126 | guava 127 | 32.0.0-jre 128 | 129 | 130 | org.zeroturnaround 131 | zt-exec 132 | 1.12 133 | 134 | 135 | org.slf4j 136 | slf4j-api 137 | 138 | 139 | org.junit.jupiter 140 | junit-jupiter 141 | 5.8.2 142 | test 143 | 144 | 145 | org.assertj 146 | assertj-core 147 | 3.23.1 148 | test 149 | 150 | 151 | 152 | -------------------------------------------------------------------------------- /quarkus-binary-s2i/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | 8 | io.quarkus.images 9 | quarkus-images-parent 10 | 1.0-SNAPSHOT 11 | 12 | 13 | quarkus-binary-s2i 14 | 15 | 16 | ${project.build.sourceDirectory}/io/quarkus/images/Build.java 17 | yyyy-MM-dd 18 | 19 | 20 | 21 | 22 | 23 | dev.jbang 24 | jbang-maven-plugin 25 | 26 | 27 | ubi8 28 | 29 | run 30 | 31 | package 32 | 33 | 34 | --dockerfile-dir=${project.basedir}/target/docker 35 | --ubi-minimal=${ubi8-min.base} 36 | --out=quay.io/quarkus/ubi-quarkus-native-binary-s2i:2.0 37 | --basedir=${project.basedir} 38 | --dry-run=${jdock.dry-run} 39 | --alias=quay.io/quarkus/ubi-quarkus-native-binary-s2i:2.0-${maven.build.timestamp} 40 | 41 | 42 | 43 | 44 | 45 | ubi9 46 | 47 | run 48 | 49 | package 50 | 51 | 52 | --dockerfile-dir=${project.basedir}/target/docker 53 | --ubi-minimal=${ubi9-min.base} 54 | --out=quay.io/quarkus/ubi9-quarkus-native-binary-s2i:2.0 55 | --basedir=${project.basedir} 56 | --dry-run=${jdock.dry-run} 57 | --alias=quay.io/quarkus/ubi9-quarkus-native-binary-s2i:2.0-${maven.build.timestamp} 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | io.quarkus.images 69 | jdock 70 | 71 | 72 | info.picocli 73 | picocli 74 | 75 | 76 | 77 | 78 | 79 | push 80 | 81 | ${project.build.sourceDirectory}/io/quarkus/images/Push.java 82 | 83 | 84 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /quarkus-binary-s2i/src/main/java/io/quarkus/images/BinaryS2IModule.java: -------------------------------------------------------------------------------- 1 | package io.quarkus.images; 2 | 3 | import io.quarkus.images.commands.Command; 4 | import io.quarkus.images.commands.CopyCommand; 5 | import io.quarkus.images.commands.RunCommand; 6 | import io.quarkus.images.modules.AbstractModule; 7 | 8 | import java.io.File; 9 | import java.util.List; 10 | 11 | public class BinaryS2IModule extends AbstractModule { 12 | 13 | public BinaryS2IModule() { 14 | super("quarkus-binary-s2i-module"); 15 | } 16 | 17 | @Override 18 | public List commands(BuildContext bc) { 19 | File f1 = new File(bc.getBasedir(), "src/main/resources/scripts/assemble"); 20 | if (!f1.isFile()) { 21 | throw new RuntimeException(f1.getAbsolutePath() + " does not exist"); 22 | } 23 | File f2 = new File(bc.getBasedir(), "src/main/resources/scripts/run"); 24 | if (!f2.isFile()) { 25 | throw new RuntimeException(f2.getAbsolutePath() + " does not exist"); 26 | } 27 | File f3 = new File(bc.getBasedir(), "src/main/resources/scripts/usage"); 28 | if (!f3.isFile()) { 29 | throw new RuntimeException(f3.getAbsolutePath() + " does not exist"); 30 | } 31 | return List.of( 32 | new CopyCommand(f1, "/usr/libexec/s2i/assemble"), 33 | new CopyCommand(f2, "/usr/libexec/s2i/run"), 34 | new CopyCommand(f3, "/usr/libexec/s2i/usage"), 35 | new RunCommand("chmod 755 -R /usr/libexec/s2i")); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /quarkus-binary-s2i/src/main/java/io/quarkus/images/Build.java: -------------------------------------------------------------------------------- 1 | ///usr/bin/env jbang "$0" "$@" ; exit $? 2 | //DEPS io.quarkus.images:jdock:1.0-SNAPSHOT 3 | //DEPS info.picocli:picocli:4.7.4 4 | //SOURCES BinaryS2IModule.java 5 | //SOURCES QuarkusBinaryS2I.java 6 | package io.quarkus.images; 7 | 8 | import picocli.CommandLine; 9 | 10 | import java.io.File; 11 | import java.util.Optional; 12 | import java.util.concurrent.Callable; 13 | 14 | @CommandLine.Command(name = "build") 15 | public class Build implements Callable { 16 | 17 | @CommandLine.Option(names = { "--ubi-minimal" }, description = "The UBI Minimal base image") 18 | private String minimal; 19 | 20 | @CommandLine.Option(names = { "--out" }, description = "The output image") 21 | private String output; 22 | 23 | @CommandLine.Option(names = { 24 | "--dockerfile-dir" }, description = "The location where the docker file should be created", defaultValue = "target/docker") 25 | private File dockerFileDir; 26 | @CommandLine.Option(names = { "--basedir" }, description = "The base directory") 27 | private File basedir; 28 | 29 | @CommandLine.Option(names = "--dry-run", description = "Just generate the docker file and skip the container build") 30 | private boolean dryRun; 31 | 32 | @CommandLine.Option(names = { "--alias" }, description = "An optional alias for the output image (ignored)") 33 | @Deprecated 34 | private Optional alias; 35 | 36 | @Override 37 | public Integer call() { 38 | JDock.setDockerFileDir(dockerFileDir); 39 | JDock.basedir = basedir; 40 | 41 | QuarkusBinaryS2I.define(minimal, output) 42 | .buildLocalImages(dryRun); 43 | 44 | return 0; 45 | } 46 | 47 | public static void main(String... args) { 48 | int exitCode = new CommandLine(new Build()).execute(args); 49 | System.exit(exitCode); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /quarkus-binary-s2i/src/main/java/io/quarkus/images/Push.java: -------------------------------------------------------------------------------- 1 | ///usr/bin/env jbang "$0" "$@" ; exit $? 2 | //DEPS io.quarkus.images:jdock:1.0-SNAPSHOT 3 | //DEPS info.picocli:picocli:4.7.4 4 | //SOURCES BinaryS2IModule.java 5 | //SOURCES QuarkusBinaryS2I.java 6 | package io.quarkus.images; 7 | 8 | import picocli.CommandLine; 9 | 10 | import java.io.File; 11 | import java.util.Optional; 12 | import java.util.concurrent.Callable; 13 | 14 | @CommandLine.Command(name = "build") 15 | public class Push implements Callable { 16 | 17 | @CommandLine.Option(names = { "--ubi-minimal" }, description = "The UBI Minimal base image") 18 | private String minimal; 19 | 20 | @CommandLine.Option(names = { "--out" }, description = "The output image") 21 | private String output; 22 | 23 | @CommandLine.Option(names = { 24 | "--dockerfile-dir" }, description = "The location where the docker file should be created", defaultValue = "target/docker") 25 | private File dockerFileDir; 26 | @CommandLine.Option(names = { "--basedir" }, description = "The base directory") 27 | private File basedir; 28 | 29 | @CommandLine.Option(names = "--dry-run", description = "Just generate the docker file and skip the container build") 30 | private boolean dryRun; 31 | 32 | @CommandLine.Option(names = { "--alias" }, description = "An optional alias for the output image") 33 | private Optional alias; 34 | 35 | @Override 36 | public Integer call() { 37 | JDock.setDockerFileDir(dockerFileDir); 38 | JDock.basedir = basedir; 39 | MultiArchImage image = QuarkusBinaryS2I.define(minimal, output); 40 | image.buildAndPush(); 41 | 42 | alias.ifPresent(s -> { 43 | if (!s.isBlank()) { 44 | MultiArchImage.createAndPushManifest(s, image.getLocalImages()); 45 | } 46 | }); 47 | 48 | return 0; 49 | } 50 | 51 | public static void main(String... args) { 52 | int exitCode = new CommandLine(new Push()).execute(args); 53 | System.exit(exitCode); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /quarkus-binary-s2i/src/main/java/io/quarkus/images/QuarkusBinaryS2I.java: -------------------------------------------------------------------------------- 1 | package io.quarkus.images; 2 | 3 | import io.quarkus.images.modules.QuarkusUserModule; 4 | import io.quarkus.images.modules.UsLangModule; 5 | 6 | import java.util.Map; 7 | 8 | public class QuarkusBinaryS2I { 9 | 10 | private static Dockerfile define(String minimal) { 11 | return Dockerfile.from(minimal) 12 | .user("root") // Switch to 1001 later 13 | .install("tar", "gzip", "gcc", "glibc-devel", "zlib-devel", "shadow-utils", "unzip", "gcc-c++", 14 | "glibc-langpack-en") 15 | .module(new UsLangModule()) 16 | .module(new QuarkusUserModule()) 17 | .module(new BinaryS2IModule()) 18 | .env("PATH", "$PATH:$JAVA_HOME/bin") 19 | .label("io.k8s.description", "Quarkus.io S2I image for running native images on Red Hat UBI 8", 20 | "io.k8s.display-name", "Quarkus.io S2I (UBI8)", 21 | "io.openshift.expose-services", "8080:http", 22 | "io.openshift.s2i.destination", "/tmp", 23 | "io.openshift.s2i.scripts-url", "image:///usr/libexec/s2i", 24 | "io.openshift.tags", "builder,quarkus,native", 25 | "maintainer", "Quarkus Team ") 26 | .user("1001") 27 | .workdir("${APP_HOME}") 28 | .expose(8080) 29 | .cmd("/usr/libexec/s2i/usage"); 30 | } 31 | 32 | static MultiArchImage define(String minimal, String output) { 33 | Dockerfile img = define(minimal); 34 | return new MultiArchImage(output, Map.of( 35 | "arm64", img, 36 | "amd64", img)); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /quarkus-binary-s2i/src/main/resources/scripts/assemble: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # This POC script is based on the openshift/source-to-image project documentation, 4 | # and loosely inspired by fabric8io-images/s2i's assemble script, but **MUCH** simplified; 5 | # a TODO future PRODUCTION version of this would probably want to re-use that script... 6 | 7 | set -ex 8 | 9 | SRC_DIR=${SRC_DIR:-'/tmp/src/'} 10 | 11 | echo "Building with uploaded src from $SRC_DIR" 12 | #ls -al $SRC_DIR 13 | 14 | echo "Assemblying" 15 | 16 | if [ "$(ls ${SRC_DIR}/*-runner | wc -l)" -eq "1" ]; then 17 | app_path=$(ls ${SRC_DIR}/*-runner) 18 | cp -v ${app_path} ${APP_HOME}/application 19 | chmod +x ${APP_HOME}/application 20 | else 21 | >&2 echo "Could not locate a native image. Have you build a native image using mvn -Pnative before?" 22 | exit 1 23 | fi 24 | 25 | chmod +rx ${APP_HOME} 26 | chmod +x ${APP_HOME}/application -------------------------------------------------------------------------------- /quarkus-binary-s2i/src/main/resources/scripts/run: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | if [ -z "$QUARKUS_OPTS" ]; then 3 | QUARKUS_OPTS="-Xmx24M -Xms16M -Xmn24M" 4 | echo "QUARKUS_OPTS environment variable was not set, using default values of ${QUARKUS_OPTS}" 5 | else 6 | echo "Using custom Java opts from environment QUARKUS_OPTS=${QUARKUS_OPTS}" 7 | fi 8 | 9 | exec ${APP_HOME}/application ${QUARKUS_OPTS} -Dquarkus.http.host=0.0.0.0 10 | -------------------------------------------------------------------------------- /quarkus-binary-s2i/src/main/resources/scripts/usage: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | cat < 2 | 5 | 4.0.0 6 | 7 | 8 | io.quarkus.images 9 | quarkus-images-parent 10 | 1.0-SNAPSHOT 11 | 12 | 13 | quarkus-distroless-base-image 14 | 15 | 16 | ${project.build.sourceDirectory}/io/quarkus/images/Build.java 17 | 18 | 19 | 20 | 21 | 22 | 23 | dev.jbang 24 | jbang-maven-plugin 25 | 26 | 27 | 28 | run 29 | 30 | package 31 | 32 | 33 | 34 | 35 | --dockerfile-dir=${project.basedir}/target/docker 36 | --out=quay.io/quarkus/quarkus-distroless-image:2.0 37 | --dry-run=${jdock.dry-run} 38 | --alias=${jdock.alias} 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | io.quarkus.images 48 | jdock 49 | 50 | 51 | info.picocli 52 | picocli 53 | 54 | 55 | 56 | 57 | 58 | push 59 | 60 | ${project.build.sourceDirectory}/io/quarkus/images/Push.java 61 | yyyy-MM-dd 62 | quay.io/quarkus/quarkus-distroless-image:2.0-${maven.build.timestamp} 63 | 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /quarkus-distroless-base-image/src/main/java/io/quarkus/images/Build.java: -------------------------------------------------------------------------------- 1 | ///usr/bin/env jbang "$0" "$@" ; exit $? 2 | //DEPS io.quarkus.images:jdock:1.0-SNAPSHOT 3 | //DEPS info.picocli:picocli:4.7.4 4 | //SOURCES QuarkusDistroless.java 5 | package io.quarkus.images; 6 | 7 | import picocli.CommandLine; 8 | 9 | import java.io.File; 10 | import java.util.Optional; 11 | import java.util.concurrent.Callable; 12 | 13 | @CommandLine.Command(name = "build") 14 | public class Build implements Callable { 15 | 16 | @CommandLine.Option(names = { "--out" }, description = "The output image") 17 | private String output; 18 | 19 | @CommandLine.Option(names = { 20 | "--dockerfile-dir" }, description = "The location where the docker file should be created", defaultValue = "target/docker") 21 | private File dockerFileDir; 22 | 23 | @CommandLine.Option(names = "--dry-run", description = "Just generate the docker file and skip the container build") 24 | private boolean dryRun; 25 | 26 | @CommandLine.Option(names = { "--alias" }, description = "An optional alias for the output image (ignored)") 27 | @Deprecated 28 | private Optional alias; 29 | 30 | @Override 31 | public Integer call() throws Exception { 32 | JDock.setDockerFileDir(dockerFileDir); 33 | QuarkusDistroless.define(output) 34 | .buildLocalImages(dryRun); 35 | 36 | return 0; 37 | } 38 | 39 | public static void main(String... args) { 40 | int exitCode = new CommandLine(new Build()).execute(args); 41 | System.exit(exitCode); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /quarkus-distroless-base-image/src/main/java/io/quarkus/images/Push.java: -------------------------------------------------------------------------------- 1 | ///usr/bin/env jbang "$0" "$@" ; exit $? 2 | //DEPS io.quarkus.images:jdock:1.0-SNAPSHOT 3 | //DEPS info.picocli:picocli:4.7.4 4 | //SOURCES QuarkusDistroless.java 5 | package io.quarkus.images; 6 | 7 | import picocli.CommandLine; 8 | 9 | import java.io.File; 10 | import java.util.Optional; 11 | import java.util.concurrent.Callable; 12 | 13 | @CommandLine.Command(name = "build") 14 | public class Push implements Callable { 15 | 16 | @CommandLine.Option(names = { "--out" }, description = "The output image") 17 | private String output; 18 | 19 | @CommandLine.Option(names = { 20 | "--dockerfile-dir" }, description = "The location where the docker file should be created", defaultValue = "target/docker") 21 | private File dockerFileDir; 22 | 23 | @CommandLine.Option(names = "--dry-run", description = "Just generate the docker file and skip the container build") 24 | private boolean dryRun; 25 | 26 | @CommandLine.Option(names = { "--alias" }, description = "An optional alias for the output image") 27 | private Optional alias; 28 | 29 | @Override 30 | public Integer call() throws Exception { 31 | JDock.setDockerFileDir(dockerFileDir); 32 | var image = QuarkusDistroless.define(output); 33 | image.buildAndPush(); 34 | 35 | alias.ifPresent(s -> { 36 | if (!s.isBlank()) { 37 | MultiArchImage.createAndPushManifest(s, image.getLocalImages()); 38 | } 39 | }); 40 | return 0; 41 | } 42 | 43 | public static void main(String... args) { 44 | int exitCode = new CommandLine(new Push()).execute(args); 45 | System.exit(exitCode); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /quarkus-distroless-base-image/src/main/java/io/quarkus/images/QuarkusDistroless.java: -------------------------------------------------------------------------------- 1 | package io.quarkus.images; 2 | 3 | import java.util.Map; 4 | 5 | public class QuarkusDistroless { 6 | 7 | static MultiArchImage define(String output) { 8 | return new MultiArchImage(output, Map.of( 9 | "arm64", getDockerFile("aarch64"), 10 | "amd64", getDockerFile("x86_64"))); 11 | } 12 | 13 | private static MultiStageDockerFile getDockerFile(String arch) { 14 | return Dockerfile.multistages() 15 | .stage("debian", Dockerfile.from("debian:stable-slim")) 16 | .stage("scratch", Dockerfile.from("gcr.io/distroless/cc")) 17 | .stage(Dockerfile.from("scratch") 18 | .copyFromStage("debian", "/lib/" + arch + "-linux-gnu/libz.so.1")); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /quarkus-graalvm-builder-image/graalvm.yaml: -------------------------------------------------------------------------------- 1 | images: 2 | # https://github.com/graalvm/graalvm-ce-builds/releases/tag/jdk-24.0.1 3 | - java-version: 24.0.1 4 | tags: jdk-24 5 | variants: 6 | - arch: amd64 7 | sha: d2c544de672e400476a09c8f1da79ecc6b774a9441e234948542c3b3e6a84bde 8 | - arch: arm64 9 | sha: a3d1be8fadfb0d3df632252301f79a9b02b47e523da66539d370c62e683d762e 10 | -------------------------------------------------------------------------------- /quarkus-graalvm-builder-image/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | 8 | io.quarkus.images 9 | quarkus-images-parent 10 | 1.0-SNAPSHOT 11 | 12 | 13 | quarkus-graalvm-builder-image 14 | 15 | 16 | ${project.build.sourceDirectory}/io/quarkus/images/Build.java 17 | ${project.basedir}/graalvm.yaml 18 | yyyy-MM-dd 19 | quarkus 20 | 21 | 22 | 23 | 24 | 25 | dev.jbang 26 | jbang-maven-plugin 27 | 28 | 29 | ubi8 30 | 31 | run 32 | 33 | package 34 | 35 | 36 | --dockerfile-dir=${project.basedir}/target/docker 37 | --ubi-minimal=${ubi8-min.base} 38 | --out=quay.io/${org}/ubi-quarkus-graalvmce-builder-image 39 | --in=${images.file} 40 | --dry-run=${jdock.dry-run} 41 | --alias=quay.io/${org}/ubi-quarkus-graalvmce-builder-image:__VERSION__-${maven.build.timestamp} 42 | 43 | 44 | 45 | 46 | 47 | ubi9 48 | 49 | run 50 | 51 | package 52 | 53 | 54 | --dockerfile-dir=${project.basedir}/target/docker 55 | --ubi-minimal=${ubi9-min.base} 56 | --out=quay.io/${org}/ubi9-quarkus-graalvmce-builder-image 57 | --in=${images.file} 58 | --dry-run=${jdock.dry-run} 59 | --alias=quay.io/${org}/ubi9-quarkus-graalvmce-builder-image:__VERSION__-${maven.build.timestamp} 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | io.quarkus.images 71 | jdock 72 | 73 | 74 | io.quarkus.images 75 | jdock-variant-helper 76 | 77 | 78 | info.picocli 79 | picocli 80 | 81 | 82 | com.fasterxml.jackson.dataformat 83 | jackson-dataformat-yaml 84 | 85 | 86 | 87 | 88 | 89 | push 90 | 91 | ${project.build.sourceDirectory}/io/quarkus/images/Push.java 92 | 93 | 94 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /quarkus-graalvm-builder-image/src/main/java/io/quarkus/images/Build.java: -------------------------------------------------------------------------------- 1 | ///usr/bin/env jbang "$0" "$@" ; exit $? 2 | //DEPS io.quarkus.images:jdock-variant-helper:1.0-SNAPSHOT 3 | //DEPS info.picocli:picocli:4.7.4 4 | //SOURCES QuarkusGraalVMBuilder.java 5 | package io.quarkus.images; 6 | 7 | import io.quarkus.images.config.Config; 8 | import io.quarkus.images.config.Tag; 9 | import picocli.CommandLine; 10 | 11 | import java.io.File; 12 | import java.util.Map; 13 | import java.util.Optional; 14 | import java.util.concurrent.Callable; 15 | 16 | @CommandLine.Command(name = "build") 17 | public class Build implements Callable { 18 | 19 | @CommandLine.Option(names = { "--out" }, description = "The output image") 20 | private String output; 21 | 22 | @CommandLine.Option(names = { 23 | "--in" }, description = "The YAML file containing the variants", defaultValue = "graalvm.yaml") 24 | private File in; 25 | 26 | @CommandLine.Option(names = { 27 | "--dockerfile-dir" }, description = "The location where the docker file should be created", defaultValue = "target/docker") 28 | private File dockerFileDir; 29 | 30 | @CommandLine.Option(names = { "--ubi-minimal" }, description = "The UBI Minimal base image") 31 | private String base; 32 | 33 | @CommandLine.Option(names = "--dry-run", description = "Just generate the docker file and skip the container build") 34 | private boolean dryRun; 35 | 36 | @CommandLine.Option(names = { "--alias" }, description = "An optional alias for the output image (ignored)") 37 | @Deprecated 38 | private Optional alias; 39 | 40 | @Override 41 | public Integer call() throws Exception { 42 | JDock.setDockerFileDir(dockerFileDir); 43 | 44 | Config config = Config.read(output, in); 45 | for (Config.ImageConfig image : config.images) { 46 | System.out 47 | .println("\uD83D\uDD25\tBuilding images " + image.fullname(config) + " : " + image.getNestedImages(config)); 48 | String groupImageName = image.fullname(config); 49 | Map architectures = QuarkusGraalVMBuilder.collect(image, base); 50 | if (architectures.size() == 1) { 51 | // Single-Arch 52 | System.out.println("\uD83D\uDD25\tBuilding single-architecture image " + groupImageName); 53 | architectures.values().iterator().next().buildLocalImage(groupImageName, dryRun); 54 | } else { 55 | // Multi-Arch 56 | System.out.println("Building multi-architecture image " + groupImageName + " with the following architectures: " 57 | + architectures.keySet()); 58 | MultiArchImage multi = new MultiArchImage(groupImageName, architectures); 59 | multi.buildLocalImages(dryRun); 60 | } 61 | Tag.createTagsIfAny(config, image, false); 62 | } 63 | 64 | return 0; 65 | } 66 | 67 | public static void main(String... args) { 68 | int exitCode = new CommandLine(new Build()).execute(args); 69 | System.exit(exitCode); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /quarkus-graalvm-builder-image/src/main/java/io/quarkus/images/Push.java: -------------------------------------------------------------------------------- 1 | ///usr/bin/env jbang "$0" "$@" ; exit $? 2 | //DEPS io.quarkus.images:jdock-variant-helper:1.0-SNAPSHOT 3 | //DEPS info.picocli:picocli:4.7.4 4 | //SOURCES QuarkusGraalVMBuilder.java 5 | package io.quarkus.images; 6 | 7 | import io.quarkus.images.config.Config; 8 | import io.quarkus.images.config.Tag; 9 | import picocli.CommandLine; 10 | 11 | import java.io.File; 12 | import java.util.Map; 13 | import java.util.Optional; 14 | import java.util.concurrent.Callable; 15 | 16 | @CommandLine.Command(name = "build") 17 | public class Push implements Callable { 18 | 19 | @CommandLine.Option(names = { "--out" }, description = "The output image") 20 | private String output; 21 | 22 | @CommandLine.Option(names = { 23 | "--in" }, description = "The YAML file containing the variants", defaultValue = "graalvm.yaml") 24 | private File in; 25 | 26 | @CommandLine.Option(names = { 27 | "--dockerfile-dir" }, description = "The location where the docker file should be created", defaultValue = "target/docker") 28 | private File dockerFileDir; 29 | 30 | @CommandLine.Option(names = { "--ubi-minimal" }, description = "The UBI Minimal base image") 31 | private String base; 32 | 33 | @CommandLine.Option(names = "--dry-run", description = "Just generate the docker file and skip the container build") 34 | private boolean dryRun; 35 | 36 | @CommandLine.Option(names = { "--alias" }, description = "An optional alias for the output image") 37 | private Optional alias; 38 | 39 | @Override 40 | public Integer call() throws Exception { 41 | JDock.setDockerFileDir(dockerFileDir); 42 | Config config = Config.read(output, in); 43 | for (Config.ImageConfig image : config.images) { 44 | if (image.isMultiArch()) { 45 | System.out 46 | .println("\uD83D\uDD25\tBuilding multi-arch image " + image.fullname(config) + " referencing " 47 | + image.getNestedImages(config)); 48 | } else { 49 | System.out 50 | .println("\uD83D\uDD25\tBuilding single-arch image " + image.fullname(config)); 51 | } 52 | String groupImageName = image.fullname(config); 53 | Map architectures = QuarkusGraalVMBuilder.collect(image, base); 54 | if (architectures.size() == 1) { 55 | // Single-Arch 56 | System.out.println("\uD83D\uDD25\tBuilding single-architecture image " + groupImageName); 57 | Buildable img = architectures.values().iterator().next(); 58 | img.buildAndPush(groupImageName); 59 | alias.ifPresent(a -> { 60 | if (!a.isBlank()) { 61 | var name = a.replace("__VERSION__", image.graalvmVersion + "-java" + image.javaVersion); 62 | img.buildAndPush(name); 63 | } 64 | }); 65 | } else { 66 | // Multi-Arch 67 | System.out.println("Building multi-architecture image " + groupImageName + " with the following architectures: " 68 | + architectures.keySet()); 69 | MultiArchImage multi = new MultiArchImage(groupImageName, architectures); 70 | multi.buildAndPush(); 71 | alias.ifPresent(a -> { 72 | if (!a.isBlank()) { 73 | String name; 74 | if (image.graalvmVersion != null) { 75 | name = a.replace("__VERSION__", image.graalvmVersion + "-java" + image.javaVersion); 76 | } else { 77 | name = a.replace("__VERSION__", "jdk-" + image.javaVersion); 78 | } 79 | MultiArchImage.createAndPushManifest(name, multi.getLocalImages()); 80 | } 81 | }); 82 | } 83 | Tag.createTagsIfAny(config, image, true); 84 | } 85 | return 0; 86 | } 87 | 88 | public static void main(String... args) { 89 | int exitCode = new CommandLine(new Push()).execute(args); 90 | System.exit(exitCode); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /quarkus-graalvm-builder-image/src/main/java/io/quarkus/images/QuarkusGraalVMBuilder.java: -------------------------------------------------------------------------------- 1 | package io.quarkus.images; 2 | 3 | import io.quarkus.images.config.Config; 4 | import io.quarkus.images.config.Variant; 5 | import io.quarkus.images.modules.*; 6 | 7 | import java.util.HashMap; 8 | import java.util.Map; 9 | 10 | public class QuarkusGraalVMBuilder { 11 | 12 | public static Dockerfile getGraalvmDockerFile(String base, String version, String javaVersion, String arch, String sha) { 13 | Dockerfile df = Dockerfile.from(base); 14 | df 15 | .user("root") 16 | .install("tar", "gzip", "gcc", "glibc-devel", "zlib-devel", "shadow-utils", "unzip", "gcc-c++", "findutils") 17 | .install("glibc-langpack-en") 18 | .module(new UsLangModule()) 19 | .module(new QuarkusUserModule()) 20 | .module(new QuarkusDirectoryModule()) 21 | .module(new UpxModule(arch)) 22 | .module(new GraalVMModule(version, arch, javaVersion, sha)) 23 | .env("PATH", "$PATH:$JAVA_HOME/bin") 24 | .label("io.k8s.description", "Quarkus.io executable image providing the `native-image` executable.", 25 | "io.k8s.display-name", "Quarkus.io executable (GraalVM Native)", 26 | "io.openshift.tags", "executable,java,quarkus,graalvm,native", 27 | "maintainer", "Quarkus Team ") 28 | .user("1001") 29 | .workdir("/project") 30 | .entrypoint("native-image"); 31 | 32 | return df; 33 | } 34 | 35 | public static Dockerfile getGraalvmDockerFile(Config.ImageConfig image, Variant variant, String base) { 36 | return getGraalvmDockerFile(base, image.graalvmVersion(), image.javaVersion(), variant.arch(), 37 | variant.sha()); 38 | } 39 | 40 | public static Map collect(Config.ImageConfig image, String base) { 41 | Map architectures = new HashMap<>(); 42 | for (Variant variant : image.variants) { 43 | Dockerfile df = QuarkusGraalVMBuilder.getGraalvmDockerFile(image, variant, base); 44 | architectures.put(variant.arch(), df); 45 | } 46 | return architectures; 47 | } 48 | } -------------------------------------------------------------------------------- /quarkus-mandrel-builder-image/mandrel-dev.yaml: -------------------------------------------------------------------------------- 1 | images: 2 | - graalvm-version: master 3 | java-version: NOT_USED 4 | # "dev" is floating, for jdk-, it's feature.interim.update_build 5 | tags: jdk-%s.%s.%s_%s 6 | variants: 7 | - sha: NOT_USED 8 | arch: amd64 9 | - sha: NOT_USED 10 | arch: arm64 11 | -------------------------------------------------------------------------------- /quarkus-mandrel-builder-image/mandrel.yaml: -------------------------------------------------------------------------------- 1 | images: 2 | # https://github.com/graalvm/mandrel/releases/tag/mandrel-23.1.7.0-Final 3 | - graalvm-version: 23.1.7.0-Final 4 | java-version: 21 5 | tags: 23.1-java21, 23.1-jdk-21, jdk-21, jdk-21.0.7 6 | variants: 7 | - sha: a9fac281199d89da8c296cf8426a6d87a07aa9c6ca474bb6f34ab9ba88ae0d55 8 | arch: amd64 9 | - sha: 25f47c3b41491e282c92f074a1b3af1940916be90ee87a9ef09d85ded5fb6a19 10 | arch: arm64 11 | 12 | # https://github.com/graalvm/mandrel/releases/tag/mandrel-24.2.1.0-Final 13 | - graalvm-version: 24.2.1.0-Final 14 | java-version: 24 15 | tags: 24.2-java24, 24.2-jdk-24, jdk-24, jdk-24.0.1 16 | variants: 17 | - sha: fe60d0227695509879e6115afb9cd130775d9d712b382e3fb1f6dfad91687049 18 | arch: amd64 19 | - sha: 36f5bfa6067201516ccb87066e419559a75966d343b42d73e0a306cdcfd2f567 20 | arch: arm64 21 | -------------------------------------------------------------------------------- /quarkus-mandrel-builder-image/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | 8 | io.quarkus.images 9 | quarkus-images-parent 10 | 1.0-SNAPSHOT 11 | 12 | 13 | quarkus-mandrel-builder-image 14 | 15 | 16 | ${project.build.sourceDirectory}/io/quarkus/images/Build.java 17 | ${project.basedir}/mandrel.yaml 18 | yyyy-MM-dd 19 | 20 | 21 | 22 | 23 | 24 | dev.jbang 25 | jbang-maven-plugin 26 | 27 | 28 | ubi8 29 | 30 | run 31 | 32 | package 33 | 34 | 35 | --dockerfile-dir=${project.basedir}/target/docker 36 | --ubi-minimal=${ubi8-min.base} 37 | --out=quay.io/quarkus/ubi-quarkus-mandrel-builder-image 38 | --in=${images.file} 39 | --dry-run=${jdock.dry-run} 40 | --alias=quay.io/quarkus/ubi-quarkus-mandrel-builder-image:__VERSION__-${maven.build.timestamp} 41 | 42 | 43 | 44 | 45 | 46 | ubi9 47 | 48 | run 49 | 50 | package 51 | 52 | 53 | --dockerfile-dir=${project.basedir}/target/docker 54 | --ubi-minimal=${ubi9-min.base} 55 | --out=quay.io/quarkus/ubi9-quarkus-mandrel-builder-image 56 | --in=${images.file} 57 | --dry-run=${jdock.dry-run} 58 | --alias=quay.io/quarkus/ubi9-quarkus-mandrel-builder-image:__VERSION__-${maven.build.timestamp} 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | io.quarkus.images 70 | jdock 71 | 72 | 73 | io.quarkus.images 74 | jdock-variant-helper 75 | 76 | 77 | info.picocli 78 | picocli 79 | 80 | 81 | com.fasterxml.jackson.dataformat 82 | jackson-dataformat-yaml 83 | 84 | 85 | 86 | 87 | 88 | push 89 | 90 | ${project.build.sourceDirectory}/io/quarkus/images/Push.java 91 | 92 | 93 | 94 | test 95 | 96 | ${project.build.sourceDirectory}/io/quarkus/images/Test.java 97 | yyyy-MM-dd 98 | quay.io/quarkus/ubi-quarkus-mandrel-builder-image:__VERSION__-${maven.build.timestamp} 99 | 100 | 101 | 102 | dev 103 | 104 | ${project.basedir}/mandrel-dev.yaml 105 | 106 | 107 | 108 | 109 | 110 | -------------------------------------------------------------------------------- /quarkus-mandrel-builder-image/src/main/java/io/quarkus/images/Build.java: -------------------------------------------------------------------------------- 1 | ///usr/bin/env jbang "$0" "$@" ; exit $? 2 | //DEPS io.quarkus.images:jdock-variant-helper:1.0-SNAPSHOT 3 | //DEPS info.picocli:picocli:4.7.4 4 | //SOURCES QuarkusMandrelBuilder.java 5 | //SOURCES JenkinsDownloader.java 6 | package io.quarkus.images; 7 | 8 | import io.quarkus.images.config.Config; 9 | import io.quarkus.images.config.Tag; 10 | import picocli.CommandLine; 11 | 12 | import java.io.File; 13 | import java.util.Map; 14 | import java.util.Optional; 15 | import java.util.concurrent.Callable; 16 | 17 | import static io.quarkus.images.QuarkusMandrelBuilder.jdkVersionAcrossArchs; 18 | 19 | @CommandLine.Command(name = "build") 20 | public class Build implements Callable { 21 | 22 | @CommandLine.Option(names = { "--out" }, description = "The output image") 23 | private String output; 24 | 25 | @CommandLine.Option(names = { 26 | "--in" }, description = "The YAML file containing the variants", defaultValue = "mandrel.yaml") 27 | private File in; 28 | 29 | @CommandLine.Option(names = { 30 | "--dockerfile-dir" }, description = "The location where the docker file should be created", defaultValue = "target/docker") 31 | private File dockerFileDir; 32 | 33 | @CommandLine.Option(names = { "--ubi-minimal" }, description = "The UBI Minimal base image") 34 | private String base; 35 | 36 | @CommandLine.Option(names = "--dry-run", description = "Just generate the docker file and skip the container build") 37 | private boolean dryRun; 38 | 39 | @CommandLine.Option(names = { "--alias" }, description = "An optional alias for the output image (ignored)") 40 | @Deprecated 41 | private Optional alias; 42 | 43 | @Override 44 | public Integer call() throws Exception { 45 | JDock.setDockerFileDir(dockerFileDir); 46 | 47 | Config config = Config.read(output, in); 48 | for (Config.ImageConfig image : config.images) { 49 | if (image.isMultiArch()) { 50 | System.out 51 | .println("\uD83D\uDD25\tBuilding multi-arch image " + image.fullname(config) + " referencing " 52 | + image.getNestedImages(config)); 53 | } else { 54 | System.out 55 | .println("\uD83D\uDD25\tBuilding single-arch image " + image.fullname(config)); 56 | } 57 | 58 | String groupImageName = image.fullname(config); 59 | Map architectures = QuarkusMandrelBuilder.collect(image, base); 60 | if (architectures.size() == 1) { 61 | // Single-Arch 62 | System.out.println("\uD83D\uDD25\tBuilding single-architecture image " + groupImageName); 63 | architectures.values().iterator().next().buildLocalImage(groupImageName, dryRun); 64 | } else { 65 | // Multi-Arch 66 | System.out.println("Building multi-architecture image " + groupImageName + " with the following architectures: " 67 | + architectures.keySet()); 68 | MultiArchImage multi = new MultiArchImage(groupImageName, architectures); 69 | multi.buildLocalImages(dryRun); 70 | } 71 | Tag.createTagsIfAny(config, image, false, jdkVersionAcrossArchs(architectures)); 72 | } 73 | 74 | return 0; 75 | } 76 | 77 | public static void main(String... args) { 78 | int exitCode = new CommandLine(new Build()).execute(args); 79 | System.exit(exitCode); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /quarkus-mandrel-builder-image/src/main/java/io/quarkus/images/JenkinsDownloader.java: -------------------------------------------------------------------------------- 1 | ///usr/bin/env jbang "$0" "$@" ; exit $? 2 | //DEPS com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.15.2 3 | //DEPS info.picocli:picocli:4.7.4 4 | package io.quarkus.images; 5 | 6 | import com.fasterxml.jackson.databind.JsonNode; 7 | import com.fasterxml.jackson.databind.ObjectMapper; 8 | import picocli.CommandLine; 9 | 10 | import java.io.IOException; 11 | import java.net.http.HttpClient; 12 | import java.util.concurrent.Callable; 13 | import java.util.regex.Matcher; 14 | import java.util.regex.Pattern; 15 | 16 | import static java.net.URI.create; 17 | import static java.net.http.HttpRequest.newBuilder; 18 | import static java.net.http.HttpResponse.BodyHandlers.ofString; 19 | 20 | @CommandLine.Command(name = "download_mandrel") 21 | public class JenkinsDownloader implements Callable { 22 | 23 | private static final HttpClient CLIENT = HttpClient.newBuilder() 24 | .followRedirects(HttpClient.Redirect.ALWAYS) 25 | .build(); 26 | 27 | // e.g. 72b1e7bbdf8b042f1729f40ee6db98dd18b47f919284ca6091773c90d6361b1d mandrel-java25-linux-amd64-25.0.0-dev7facd038f81.tar.gz 28 | private static final int MIN_SHA256_FILE_LENGTH = 75; 29 | 30 | public enum Arch { 31 | aarch64, 32 | amd64 33 | } 34 | 35 | @CommandLine.Option(names = { "--arch" }, description = "aarch64/amd64", required = true) 36 | private Arch arch; 37 | 38 | // This is a stable URL. Contact: karm@redhat.com 39 | private static final String BASE_URL = "https://ci.modcluster.io/view/Mandrel/job/mandrel-master-linux-build-matrix"; 40 | // Works with Jenkins 2.492.1 41 | private static final String API = "/api/json?tree=activeConfigurations[name,lastStableBuild[number,url]]"; 42 | // The point of this dev image is to use the latest. There might even not be any ga yet. 43 | private static final String JDK_RELEASE = "ea"; 44 | // "Labels" are arbitrary strings configured on https://ci.modcluster.io Jenkins. 45 | private static final String AMD64_LABEL = "el8"; 46 | private static final String AARCH64_LABEL = "el8_aarch64"; 47 | private static final Pattern VERSION_LABEL_RELEASE = Pattern.compile( 48 | "^(?=.*JDK_VERSION=(?\\d+))(?=.*LABEL=(?