├── .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