Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 84 additions & 0 deletions .github/workflows/build-java-image.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
name: Build & Push Java App

on:
pull_request:
branches: [main]
push:
branches: [main]
workflow_dispatch:

env:
IMAGE_NAME: techiescamp/java-image
DOCKERFILE: ./Dockerfile

jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Set up Java 17 and Maven cache
uses: actions/setup-java@v5
with:
distribution: temurin
java-version: "21"
cache: maven

- name: Set up QEMU
uses: docker/setup-qemu-action@v3

- name: Set up Buildx
uses: docker/setup-buildx-action@v3

- name: Build with Maven (tests on)
working-directory: ./java-app
run: ./mvnw clean package

- name: Lint Dockerfile (hadolint)
if: ${{ github.event_name == 'pull_request' }}
uses: hadolint/hadolint-action@v3.1.0
with:
dockerfile: ${{ env.DOCKERFILE }}
failure-threshold: error

- name: Docker build (no push) for PR
if: ${{ github.event_name == 'pull_request' }}
uses: docker/build-push-action@v6
with:
context: .
file: ${{ env.DOCKERFILE }}
platforms: linux/amd64
tags: ${{ env.IMAGE_NAME }}:pr-${{ github.event.number }}
load: true
push: false

- name: Trivy scan (image)
if: ${{ github.event_name == 'pull_request' }}
uses: aquasecurity/trivy-action@0.28.0
with:
scan-type: image
image-ref: ${{ env.IMAGE_NAME }}:pr-${{ github.event.number }}
severity: HIGH,CRITICAL
ignore-unfixed: true
format: table
exit-code: '0'

- name: Login to Docker Hub
if: ${{ github.event_name == 'push' }}
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}

- name: Docker build & push (multi-arch)
if: ${{ github.event_name == 'push' }}
uses: docker/build-push-action@v6
with:
context: .
file: ${{ env.DOCKERFILE }}
platforms: linux/amd64,linux/arm64
push: true
tags: |
${{ env.IMAGE_NAME }}:1.0.${{ github.run_number }}
${{ env.IMAGE_NAME }}:latest
30 changes: 30 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
.gradle/
build/
!gradle/wrapper/gradle-wrapper.jar

target/
!mvnw
!mvnw.cmd
.mvn/wrapper/maven-wrapper.jar

.idea/
*.iml

.classpath
.project
.settings/

.DS_Store
Thumbs.db

*.log
*.tmp
*.bak
*.swp

*.class

*.war
*.jar
application-*.properties
application-*.yml
14 changes: 14 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Use the distroless Java base image
FROM gcr.io/distroless/java21-debian12

Check failure on line 2 in Dockerfile

View workflow job for this annotation

GitHub Actions / build

DL3006 warning: Always tag the version of an image explicitly

# Set the working directory in the container
WORKDIR /app

# Copy the JAR file into the container at /app
COPY java-app/target/*.jar java.jar

# Expose the port your app runs on
EXPOSE 8080

# Run the jar file
CMD ["java.jar"]
4 changes: 4 additions & 0 deletions java-app/.mvn/wrapper/maven-wrapper.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Maven to use
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.9/apache-maven-3.9.9-bin.zip
# Wrapper bootstrap JAR to download when missing
wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.3.2/maven-wrapper-3.3.2.jar
53 changes: 53 additions & 0 deletions java-app/mvnw
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
#!/bin/sh
set -e

# Resolve script dir
PRG="$0"
while [ -h "$PRG" ]; do
ls=$(ls -ld "$PRG"); link=$(expr "$ls" : '.*-> \(.*\)$')
if expr "$link" : '/.*' >/dev/null; then PRG="$link"; else PRG=$(dirname "$PRG")"/$link"; fi
done
BASEDIR=$(cd "$(dirname "$PRG")" >/dev/null 2>&1 && pwd -P)
APP_HOME="$BASEDIR"

WRAPPER_DIR="$APP_HOME/.mvn/wrapper"
WRAPPER_JAR="$WRAPPER_DIR/maven-wrapper.jar"
WRAPPER_PROPS="$WRAPPER_DIR/maven-wrapper.properties"

# Pick Java
if [ -n "$JAVA_HOME" ]; then JAVA_CMD="$JAVA_HOME/bin/java"; else JAVA_CMD="java"; fi

# Find project base dir (folder that has pom.xml)
if [ -z "$MAVEN_PROJECTBASEDIR" ]; then
CUR="$APP_HOME"
while [ "$CUR" != "/" ]; do
if [ -f "$CUR/pom.xml" ]; then MAVEN_PROJECTBASEDIR="$CUR"; break; fi
CUR=$(cd "$CUR/.." && pwd -P)
done
[ -n "$MAVEN_PROJECTBASEDIR" ] || MAVEN_PROJECTBASEDIR="$APP_HOME"
fi

# Download wrapper JAR if missing
if [ ! -f "$WRAPPER_JAR" ]; then
mkdir -p "$WRAPPER_DIR"
if [ ! -f "$WRAPPER_PROPS" ]; then
echo "Missing $WRAPPER_PROPS" >&2; exit 1
fi
WRAPPER_URL=$(grep -E '^wrapperUrl=' "$WRAPPER_PROPS" | cut -d'=' -f2-)
if [ -z "$WRAPPER_URL" ]; then
echo "wrapperUrl not found in $WRAPPER_PROPS" >&2; exit 1
fi
echo "Downloading Maven Wrapper JAR from $WRAPPER_URL ..."
if command -v curl >/dev/null 2>&1; then
curl -fsSL -o "$WRAPPER_JAR" "$WRAPPER_URL"
elif command -v wget >/dev/null 2>&1; then
wget -q -O "$WRAPPER_JAR" "$WRAPPER_URL"
else
echo "Need curl or wget to download maven-wrapper.jar" >&2; exit 1
fi
fi

exec "$JAVA_CMD" \
-Dmaven.wrapper.log.level=INFO \
"-Dmaven.multiModuleProjectDirectory=$MAVEN_PROJECTBASEDIR" \
-cp "$WRAPPER_JAR" org.apache.maven.wrapper.MavenWrapperMain "$@"
46 changes: 46 additions & 0 deletions java-app/mvnw.cmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
@ECHO OFF
SETLOCAL

SET "BASEDIR=%~dp0"
SET "WRAPPER_DIR=%BASEDIR%.mvn\wrapper"
SET "WRAPPER_JAR=%WRAPPER_DIR%\maven-wrapper.jar"
SET "WRAPPER_PROPS=%WRAPPER_DIR%\maven-wrapper.properties"

IF EXIST "%JAVA_HOME%\bin\java.exe" (
SET "JAVA_EXE=%JAVA_HOME%\bin\java.exe"
) ELSE (
SET "JAVA_EXE=java"
)

IF NOT EXIST "%WRAPPER_JAR%" (
IF NOT EXIST "%WRAPPER_DIR%" MKDIR "%WRAPPER_DIR%"
FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%WRAPPER_PROPS%") DO (
IF "%%A"=="wrapperUrl" SET "WRAPPER_URL=%%B"
)
IF "%WRAPPER_URL%"=="" (
ECHO wrapperUrl not found in %WRAPPER_PROPS%
EXIT /B 1
)
ECHO Downloading Maven Wrapper JAR from %WRAPPER_URL%
POWERSHELL -NoProfile -ExecutionPolicy Bypass -Command ^
"Invoke-WebRequest -Uri '%WRAPPER_URL%' -OutFile '%WRAPPER_JAR%'"
)

REM Find project base dir
SET "MAVEN_PROJECTBASEDIR="
SET "CUR=%CD%"
:findPom
IF EXIST "%CUR%\pom.xml" (
SET "MAVEN_PROJECTBASEDIR=%CUR%"
) ELSE (
CD /D "%CUR%\.."
SET "CUR=%CD%"
IF "%CUR%"=="%SystemDrive%\" GOTO afterFind
GOTO findPom
)
:afterFind
IF "%MAVEN_PROJECTBASEDIR%"=="" SET "MAVEN_PROJECTBASEDIR=%BASEDIR%"

"%JAVA_EXE%" -Dmaven.wrapper.log.level=INFO ^
"-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^
-cp "%WRAPPER_JAR%" org.apache.maven.wrapper.MavenWrapperMain %*
52 changes: 52 additions & 0 deletions java-app/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.3.4</version>
<relativePath/>
</parent>

<groupId>com.techiescamp</groupId>
<artifactId>java-app</artifactId>
<version>1.0.0</version>
<name>java-app</name>
<description>Spring Boot application</description>

<properties>
<java.version>21</java.version>
</properties>

<dependencies>
<!-- Core -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>

<!-- Web API (remove if not needed) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<!-- Tests -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
20 changes: 20 additions & 0 deletions java-app/src/main/java/springboot/Application.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package springboot;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@SpringBootApplication
@RestController
public class Application {

public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}

@GetMapping("/")
public String index() {
return "This is a demo Spring Boot application";
}
}
26 changes: 26 additions & 0 deletions java-app/src/test/java/springboot/ApplicationTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package springboot;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.web.servlet.MockMvc;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@SpringBootTest
@AutoConfigureMockMvc
public class ApplicationTest {

@Autowired
private MockMvc mockMvc;

@Test
public void indexEndpointReturnsExpectedMessage() throws Exception {
mockMvc.perform(get("/"))
.andExpect(status().isOk())
.andExpect(content().string("This is a demo Spring Boot application"));
}
}