Skip to content

Commit 2bc79a0

Browse files
committed
Provide native image builds
fixes #24
1 parent ecba405 commit 2bc79a0

File tree

7 files changed

+234
-22
lines changed

7 files changed

+234
-22
lines changed

.github/workflows/build.yaml

Lines changed: 73 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,4 +41,76 @@ jobs:
4141
REGISTRY_TOKEN: ${{ secrets.GITHUB_TOKEN }}
4242
run: |
4343
export IMAGE_TAGS="${IMAGE_RAW_TAGS//$'\n'/,}"
44-
mvn -B -ntp -P build-image,publish verify
44+
mvn -B -ntp -P ci,build-image,publish package
45+
46+
build-maven-native:
47+
runs-on: ubuntu-latest
48+
permissions:
49+
packages: write
50+
steps:
51+
- uses: actions/checkout@v3
52+
- name: Inject slug/short variables
53+
uses: rlespinasse/github-slug-action@v4
54+
- name: Set up JDK 17
55+
uses: actions/setup-java@v3
56+
with:
57+
java-version: "17"
58+
distribution: "temurin"
59+
cache: "maven"
60+
- name: Set up QEMU
61+
uses: docker/setup-qemu-action@v3
62+
- name: Set up Docker Buildx
63+
uses: docker/setup-buildx-action@v2
64+
- name: Extract metadata (tags, labels) for Docker
65+
id: meta
66+
uses: docker/metadata-action@v5
67+
with:
68+
images: ghcr.io/${{ github.repository }}
69+
tags: |
70+
type=sha
71+
type=ref,event=branch
72+
- name: Build with Maven
73+
env:
74+
IMAGE_NAME: ghcr.io/${{ github.repository }}-native
75+
IMAGE_RAW_TAGS: ${{ steps.meta.outputs.tags }}
76+
REGISTRY_USER: ${{ github.repository_owner }}
77+
REGISTRY_TOKEN: ${{ secrets.GITHUB_TOKEN }}
78+
run: |
79+
export IMAGE_TAGS="${IMAGE_RAW_TAGS//$'\n'/,}"
80+
mvn -B -ntp -P ci,native,build-native-image,publish package
81+
82+
build-maven-native-keycloak:
83+
runs-on: ubuntu-latest
84+
permissions:
85+
packages: write
86+
steps:
87+
- uses: actions/checkout@v3
88+
- name: Inject slug/short variables
89+
uses: rlespinasse/github-slug-action@v4
90+
- name: Set up JDK 17
91+
uses: actions/setup-java@v3
92+
with:
93+
java-version: "17"
94+
distribution: "temurin"
95+
cache: "maven"
96+
- name: Set up QEMU
97+
uses: docker/setup-qemu-action@v2
98+
- name: Set up Docker Buildx
99+
uses: docker/setup-buildx-action@v2
100+
- name: Extract metadata (tags, labels) for Docker
101+
id: meta
102+
uses: docker/metadata-action@v5
103+
with:
104+
images: ghcr.io/${{ github.repository }}
105+
tags: |
106+
type=sha
107+
type=ref,event=branch
108+
- name: Build with Maven
109+
env:
110+
IMAGE_NAME: ghcr.io/${{ github.repository }}-native-keycloak
111+
IMAGE_RAW_TAGS: ${{ steps.meta.outputs.tags }}
112+
REGISTRY_USER: ${{ github.repository_owner }}
113+
REGISTRY_TOKEN: ${{ secrets.GITHUB_TOKEN }}
114+
run: |
115+
export IMAGE_TAGS="${IMAGE_RAW_TAGS//$'\n'/,}"
116+
mvn -B -ntp -P ci,native,keycloak,build-native-image,publish package

.github/workflows/release.yaml

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ jobs:
7474
tags: |
7575
type=raw,value=${{ github.event.inputs.release-version }}
7676
${{ (github.event.inputs.latest-image == 'y' && 'type=raw,value=latest') || '' }}
77-
- name: Build with Maven
77+
- name: Build image
7878
env:
7979
IMAGE_NAME: ghcr.io/${{ github.repository }}
8080
IMAGE_RAW_TAGS: ${{ steps.meta.outputs.tags }}
@@ -84,7 +84,29 @@ jobs:
8484
run: |
8585
export IMAGE_TAGS="${IMAGE_RAW_TAGS//$'\n'/,}"
8686
if [[ $LATEST_TAG == "y" ]]; then export IMAGE_TAGS=$IMAGE_TAGS,ghcr.io/it-at-m/appswitcher-server:latest; fi
87-
mvn -B -ntp -DskipTests -P build-image,publish verify
87+
mvn -B -ntp -DskipTests -P ci,build-image,publish verify
88+
- name: Build native image (default profile)
89+
env:
90+
IMAGE_NAME: ghcr.io/${{ github.repository }}-native
91+
IMAGE_RAW_TAGS: ${{ steps.meta.outputs.tags }}
92+
REGISTRY_USER: ${{ github.repository_owner }}
93+
REGISTRY_TOKEN: ${{ secrets.GITHUB_TOKEN }}
94+
LATEST_TAG: ${{ (github.event.inputs.latest-image == 'y' && 'y') || 'n' }}
95+
run: |
96+
export IMAGE_TAGS="${IMAGE_RAW_TAGS//$'\n'/,}"
97+
if [[ $LATEST_TAG == "y" ]]; then export IMAGE_TAGS=$IMAGE_TAGS,$IMAGE_NAME:latest; fi
98+
mvn -B -ntp -DskipTests -P ci,native,build-native-image,publish verify
99+
- name: Build native image (keycloak profile)
100+
env:
101+
IMAGE_NAME: ghcr.io/${{ github.repository }}-native-keycloak
102+
IMAGE_RAW_TAGS: ${{ steps.meta.outputs.tags }}
103+
REGISTRY_USER: ${{ github.repository_owner }}
104+
REGISTRY_TOKEN: ${{ secrets.GITHUB_TOKEN }}
105+
LATEST_TAG: ${{ (github.event.inputs.latest-image == 'y' && 'y') || 'n' }}
106+
run: |
107+
export IMAGE_TAGS="${IMAGE_RAW_TAGS//$'\n'/,}"
108+
if [[ $LATEST_TAG == "y" ]]; then export IMAGE_TAGS=$IMAGE_TAGS,$IMAGE_NAME:latest; fi
109+
mvn -B -ntp -DskipTests -P ci,keycloak,native,build-native-image,publish verify
88110
- name: Create GitHub Release
89111
id: create_release
90112
uses: softprops/action-gh-release@v2

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,12 @@ Keycloak integration can be enabled by enabling the Spring profile `keycloak` (s
123123

124124
You can use the official container image [ghrc.io/it-at-m/appswitcher-server](https://github.com/it-at-m/appswitcher-server/pkgs/container/appswitcher-server). To provide your [custom Applications](#custom-applications) create a custom `application.yml` containing your applications under the key `appswitcher.apps.*` and mount the file as a volume at `/workspace/config/application.yml`.
125125

126+
If you prefer to run a native image for faster startup times, there a two image variants available:
127+
128+
- [ghrc.io/it-at-m/appswitcher-server-native](https://github.com/it-at-m/appswitcher-server/pkgs/container/appswitcher-server-native)
129+
- [ghrc.io/it-at-m/appswitcher-server-native-keycloak](https://github.com/it-at-m/appswitcher-server/pkgs/container/appswitcher-server-native-keycloak) (for [Keycloak integration](#keycloak-integration) support)
130+
131+
126132
### Kubernetes
127133

128134
If you want to deploy appswitcher-server on a Kubernetes cluster, you can use the [official Helm chart][helm-chart-github].

pom.xml

Lines changed: 94 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040
<java.version>17</java.version>
4141
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
4242
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
43-
<image.name>itatm/${project.artifactId}:${project.version}</image.name>
43+
<image.name>itatm/${project.artifactId}</image.name>
4444
</properties>
4545

4646
<dependencies>
@@ -116,6 +116,11 @@
116116
<groupId>org.springframework.boot</groupId>
117117
<artifactId>spring-boot-maven-plugin</artifactId>
118118
</plugin>
119+
<!-- native image building -->
120+
<plugin>
121+
<groupId>org.graalvm.buildtools</groupId>
122+
<artifactId>native-maven-plugin</artifactId>
123+
</plugin>
119124
<plugin>
120125
<groupId>org.apache.maven.plugins</groupId>
121126
<artifactId>maven-enforcer-plugin</artifactId>
@@ -176,6 +181,7 @@
176181
<goals>
177182
<goal>check</goal>
178183
</goals>
184+
<phase>test</phase>
179185
</execution>
180186
</executions>
181187
</plugin>
@@ -211,6 +217,7 @@
211217
<goals>
212218
<goal>check</goal>
213219
</goals>
220+
<phase>test</phase>
214221
</execution>
215222
</executions>
216223
</plugin>
@@ -247,10 +254,33 @@
247254

248255
<profiles>
249256
<profile>
250-
<id>build-image</id>
257+
<id>lhm</id>
258+
<build>
259+
<plugins>
260+
<plugin>
261+
<groupId>org.springframework.boot</groupId>
262+
<artifactId>spring-boot-maven-plugin</artifactId>
263+
<configuration>
264+
<docker>
265+
<builderRegistry>
266+
<username>${env.DOCKERHUB_USERNAME}</username>
267+
<password>${env.DOCKERHUB_PASSWORD}</password>
268+
<url>https://index.docker.io/v1/</url>
269+
</builderRegistry>
270+
</docker>
271+
</configuration>
272+
</plugin>
273+
</plugins>
274+
</build>
275+
</profile>
276+
<profile>
277+
<id>ci</id>
251278
<properties>
252279
<image.name>${env.IMAGE_NAME}</image.name>
253280
</properties>
281+
</profile>
282+
<profile>
283+
<id>build-image</id>
254284
<build>
255285
<plugins>
256286
<plugin>
@@ -265,13 +295,14 @@
265295
<NO_PROXY>${env.NO_PROXY}</NO_PROXY>
266296
<BP_HEALTH_CHECKER_ENABLED>true</BP_HEALTH_CHECKER_ENABLED>
267297
</env>
298+
<verboseLogging>true</verboseLogging>
268299
<tags>${env.IMAGE_TAGS}</tags>
269300
<builder>paketobuildpacks/builder-jammy-base:latest</builder>
270301
<buildpacks>
271302
<buildpack>
272303
urn:cnb:builder:paketo-buildpacks/java</buildpack>
273304
<buildpack>
274-
gcr.io/paketo-buildpacks/health-checker:latest</buildpack>
305+
docker.io/paketobuildpacks/health-checker:latest</buildpack>
275306
</buildpacks>
276307
</image>
277308
</configuration>
@@ -287,6 +318,66 @@
287318
</plugins>
288319
</build>
289320
</profile>
321+
<profile>
322+
<id>build-native-image</id>
323+
<build>
324+
<plugins>
325+
<plugin>
326+
<groupId>org.springframework.boot</groupId>
327+
<artifactId>spring-boot-maven-plugin</artifactId>
328+
<configuration>
329+
<image>
330+
<name>${image.name}:${project.version}</name>
331+
<env>
332+
<HTTP_PROXY>${env.HTTP_PROXY}</HTTP_PROXY>
333+
<HTTPS_PROXY>${env.HTTPS_PROXY}</HTTPS_PROXY>
334+
<NO_PROXY>${env.NO_PROXY}</NO_PROXY>
335+
<BP_HEALTH_CHECKER_ENABLED>true</BP_HEALTH_CHECKER_ENABLED>
336+
</env>
337+
<verboseLogging>true</verboseLogging>
338+
<tags>${env.IMAGE_TAGS}</tags>
339+
<builder>paketobuildpacks/builder-jammy-base:latest</builder>
340+
<buildpacks>
341+
<buildpack>
342+
urn:cnb:builder:paketo-buildpacks/java</buildpack>
343+
<buildpack>
344+
urn:cnb:builder:paketo-buildpacks/native-image</buildpack>
345+
<buildpack>
346+
docker.io/paketobuildpacks/health-checker:latest</buildpack>
347+
</buildpacks>
348+
</image>
349+
</configuration>
350+
<executions>
351+
<execution>
352+
<id>build-image</id>
353+
<goals>
354+
<goal>build-image-no-fork</goal>
355+
</goals>
356+
</execution>
357+
</executions>
358+
</plugin>
359+
</plugins>
360+
</build>
361+
</profile>
362+
<profile>
363+
<id>keycloak</id>
364+
<build>
365+
<plugins>
366+
<plugin>
367+
<groupId>org.springframework.boot</groupId>
368+
<artifactId>spring-boot-maven-plugin</artifactId>
369+
<executions>
370+
<execution>
371+
<id>process-aot</id>
372+
<configuration>
373+
<profiles>keycloak</profiles>
374+
</configuration>
375+
</execution>
376+
</executions>
377+
</plugin>
378+
</plugins>
379+
</build>
380+
</profile>
290381
<profile>
291382
<id>publish</id>
292383
<build>

src/main/java/de/muenchen/oss/appswitcher/AppswitcherApplication.java

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,18 +22,28 @@
2222
*/
2323
package de.muenchen.oss.appswitcher;
2424

25+
import java.util.LinkedHashMap;
26+
import java.util.Map;
27+
2528
import org.apache.tomcat.util.http.Rfc6265CookieProcessor;
29+
import org.springframework.aot.hint.MemberCategory;
30+
import org.springframework.aot.hint.RuntimeHints;
31+
import org.springframework.aot.hint.RuntimeHintsRegistrar;
32+
import org.springframework.aot.hint.TypeReference;
2633
import org.springframework.boot.SpringApplication;
2734
import org.springframework.boot.autoconfigure.SpringBootApplication;
2835
import org.springframework.boot.web.embedded.tomcat.TomcatContextCustomizer;
2936
import org.springframework.context.annotation.Bean;
37+
import org.springframework.context.annotation.ImportRuntimeHints;
3038
import org.springframework.context.annotation.Scope;
3139
import org.springframework.context.annotation.ScopedProxyMode;
3240
import org.springframework.web.context.WebApplicationContext;
3341

42+
import de.muenchen.oss.appswitcher.AppswitcherApplication.MyRuntimeHints;
3443
import de.muenchen.oss.appswitcher.session.SessionBean;
3544

3645
@SpringBootApplication
46+
@ImportRuntimeHints(value = { MyRuntimeHints.class })
3747
public class AppswitcherApplication {
3848

3949
public static void main(String[] args) {
@@ -43,7 +53,7 @@ public static void main(String[] args) {
4353
// Um Session Cookie auch als 3Party-Cookie in iframes zur Verfügung zu haben:
4454
// https://stackoverflow.com/a/60860531
4555
@Bean
46-
public TomcatContextCustomizer sameSiteCookiesConfig() {
56+
TomcatContextCustomizer sameSiteCookiesConfig() {
4757
return context -> {
4858
final Rfc6265CookieProcessor cookieProcessor = new Rfc6265CookieProcessor();
4959
cookieProcessor.setSameSiteCookies("NONE");
@@ -57,8 +67,21 @@ public TomcatContextCustomizer sameSiteCookiesConfig() {
5767
// Proxy to inject it into our singleton-scoped @Controller
5868
proxyMode = ScopedProxyMode.TARGET_CLASS
5969
)
60-
public SessionBean todos() {
70+
SessionBean todos() {
6171
return new SessionBean();
6272
}
6373

74+
static class MyRuntimeHints implements RuntimeHintsRegistrar {
75+
76+
@Override
77+
public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
78+
// see https://github.com/spring-projects/spring-boot/issues/34206
79+
hints.reflection().registerType(Map.class, MemberCategory.values());
80+
hints.reflection().registerType(LinkedHashMap.class, MemberCategory.values());
81+
hints.reflection().registerType(TypeReference.of("java.util.Map$Entry"), MemberCategory.values());
82+
hints.reflection().registerType(TypeReference.of("java.util.LinkedHashMap$Entry"), MemberCategory.values());
83+
}
84+
85+
}
86+
6487
}

src/main/java/de/muenchen/oss/appswitcher/AppswitcherProperties.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
*/
2323
package de.muenchen.oss.appswitcher;
2424

25+
import java.util.LinkedHashMap;
2526
import java.util.Map;
2627

2728
import org.springframework.boot.context.properties.ConfigurationProperties;
@@ -36,7 +37,7 @@
3637
public class AppswitcherProperties {
3738

3839
@NestedConfigurationProperty
39-
private Map<String, AppConfigurationProperties> apps;
40+
private Map<String, AppConfigurationProperties> apps = new LinkedHashMap<>();
4041

4142
@NestedConfigurationProperty
4243
private KeycloakConfigurationProperties keycloak;

0 commit comments

Comments
 (0)