commit 74a57f251e1db5354a0707a6ddad378c99cfb5ea Author: gitea-ci Date: Wed Jul 22 01:06:34 2026 +0800 Initial commit: demo-java-api with CI/CD diff --git a/.gitea/workflows/build.yml b/.gitea/workflows/build.yml new file mode 100644 index 0000000..f6f4620 --- /dev/null +++ b/.gitea/workflows/build.yml @@ -0,0 +1,77 @@ +name: Build & Deploy Java API + +on: + push: + branches: [main] + +env: + REGISTRY: harbor.cjjhc.top/library + APP_NAME: demo-java-api + +jobs: + build: + runs-on: self-hosted + + steps: + - name: Prepare workspace + run: | + WORK=/root/.cache/${{ env.APP_NAME }} + rm -rf "$WORK" + mkdir -p "$WORK" + + - name: Checkout code + uses: actions/checkout@v4 + with: + path: ${{ env.APP_NAME }} + + - name: Set up JDK 17 + uses: actions/setup-java@v4 + with: + java-version: '17' + distribution: 'temurin' + + - name: Cache Maven packages + uses: actions/cache@v3 + with: + path: ~/.m2/repository + key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} + restore-keys: | + ${{ runner.os }}-maven- + + - name: Login to Harbor + run: docker login ${{ env.REGISTRY }} -u admin -p "${{ secrets.HARBOR_PASSWORD }}" + + - name: Build with Maven + run: | + cd ${{ env.APP_NAME }} + mvn clean package -DskipTests + + - name: Build & push Docker image + run: | + cd ${{ env.APP_NAME }} + SHORT=${GITHUB_SHA::8} + TAG="${{ env.REGISTRY }}/${{ env.APP_NAME }}:${SHORT}" + LATEST="${{ env.REGISTRY }}/${{ env.APP_NAME }}:latest" + + docker build -t "${TAG}" -t "${LATEST}" . + docker push "${TAG}" + docker push "${LATEST}" + echo "✓ Pushed ${TAG}" + + - name: Update image tag in values.yaml (commit-back GitOps) + run: | + cd ${{ env.APP_NAME }} + SHORT=${GITHUB_SHA::8} + + # Update tag in values.yaml + sed -i "s/^ tag:.*/ tag: \"${SHORT}\"/" deploy/helm/values.yaml + + # Configure git + git config user.email "ci@local" + git config user.name "gitea-ci" + + # Commit and push + git add deploy/helm/values.yaml + git commit -m "ci: update image tag to ${SHORT}" || echo "nothing to commit" + git push origin main + echo "✓ Pushed values.yaml with tag ${SHORT}" diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..9a4c78d --- /dev/null +++ b/Dockerfile @@ -0,0 +1,19 @@ +# Multi-stage build for Spring Boot +FROM maven:3.9-eclipse-temurin-17 AS builder + +WORKDIR /app +COPY pom.xml . +COPY src ./src +RUN mvn clean package -DskipTests + +# Production stage +FROM eclipse-temurin:17-jre-alpine + +WORKDIR /app +COPY --from=builder /app/target/*.jar app.jar + +EXPOSE 8080 + +HEALTHCHECK --interval=30s --timeout=3s CMD wget -q -O- http://localhost:8080/actuator/health || exit 1 + +ENTRYPOINT ["java", "-jar", "app.jar"] diff --git a/README.md b/README.md new file mode 100644 index 0000000..833a324 --- /dev/null +++ b/README.md @@ -0,0 +1,44 @@ +# Demo Java API + +A simple Spring Boot API for CI/CD demonstration. + +## API Endpoints + +| Endpoint | Method | Description | +|----------|--------|-------------| +| `/` | GET | Returns API info with timestamp | +| `/healthz` | GET | Health check endpoint | + +## Local Development + +```bash +./mvnw spring-boot:run +``` + +## Build Docker Image + +```bash +docker build -t demo-java-api . +docker run -p 8080:8080 demo-java-api +``` + +## Project Structure + +``` +demo-java-api/ +├── pom.xml # Maven config +├── Dockerfile # Multi-stage Docker build +├── src/ +│ └── main/ +│ ├── java/com/example/demo/ +│ │ └── DemoJavaApiApplication.java +│ └── resources/ +│ └── application.properties +├── deploy/helm/ +│ ├── Chart.yaml +│ ├── values.yaml +│ └── templates/ +│ └── deployment.yaml +└── .gitea/workflows/ + └── build.yml # Gitea CI workflow +``` diff --git a/deploy/helm/Chart.yaml b/deploy/helm/Chart.yaml new file mode 100644 index 0000000..4f47262 --- /dev/null +++ b/deploy/helm/Chart.yaml @@ -0,0 +1,6 @@ +apiVersion: v2 +name: demo-java-api +description: A Helm chart for demo-java-api +type: application +version: 1.0.0 +appVersion: "1.0.0" diff --git a/deploy/helm/templates/deployment.yaml b/deploy/helm/templates/deployment.yaml new file mode 100644 index 0000000..1531d2a --- /dev/null +++ b/deploy/helm/templates/deployment.yaml @@ -0,0 +1,88 @@ +{{- define "fullname" -}} +{{- .Chart.Name }} +{{- end }} + +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Chart.Name }} + labels: + app: {{ .Chart.Name }} +spec: + replicas: {{ .Values.replicaCount }} + selector: + matchLabels: + app: {{ .Chart.Name }} + template: + metadata: + labels: + app: {{ .Chart.Name }} + spec: + containers: + - name: {{ .Chart.Name }} + image: "{{ .Values.image.registry }}/{{ .Values.image.name }}:{{ .Values.image.tag }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + ports: + - containerPort: {{ .Values.service.port }} + name: http + livenessProbe: + {{- if .Values.probes.liveness.enabled }} + httpGet: + path: {{ .Values.probes.liveness.path }} + port: {{ .Values.probes.liveness.port }} + initialDelaySeconds: 30 + periodSeconds: 10 + {{- else }} + exec: + command: ["true"] + {{- end }} + readinessProbe: + {{- if .Values.probes.readiness.enabled }} + httpGet: + path: {{ .Values.probes.readiness.path }} + port: {{ .Values.probes.readiness.port }} + initialDelaySeconds: 5 + periodSeconds: 5 + {{- else }} + exec: + command: ["true"] + {{- end }} + resources: + {{- toYaml .Values.resources | nindent 12 }} + env: + {{- toYaml .Values.env | nindent 12 }} +--- +apiVersion: v1 +kind: Service +metadata: + name: {{ .Chart.Name }} +spec: + type: {{ .Values.service.type }} + ports: + - port: {{ .Values.service.port }} + targetPort: http + protocol: TCP + name: http + selector: + app: {{ .Chart.Name }} +{{- if .Values.ingress.enabled }} +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ .Chart.Name }} + annotations: + traefik.ingress.kubernetes.io/router.entrypoints: web +spec: + rules: + - host: {{ .Values.ingress.host }} + http: + paths: + - path: {{ .Values.ingress.path }} + pathType: Prefix + backend: + service: + name: {{ .Chart.Name }} + port: + number: {{ .Values.service.port }} +{{- end }} diff --git a/deploy/helm/values.yaml b/deploy/helm/values.yaml new file mode 100644 index 0000000..994cd5c --- /dev/null +++ b/deploy/helm/values.yaml @@ -0,0 +1,38 @@ +# Default values +image: + registry: harbor.cjjhc.top/library + name: demo-java-api + tag: "latest" + pullPolicy: Always + +replicaCount: 2 + +service: + type: ClusterIP + port: 8080 + +ingress: + enabled: true + className: traefik + host: demo-java-api.local + path: / + +resources: + requests: + cpu: 500m + memory: 512Mi + limits: + cpu: 1000m + memory: 1Gi + +env: [] + +probes: + liveness: + enabled: true + path: /healthz + port: 8080 + readiness: + enabled: true + path: /healthz + port: 8080 diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..0710147 --- /dev/null +++ b/pom.xml @@ -0,0 +1,43 @@ + + + 4.0.0 + + + org.springframework.boot + spring-boot-starter-parent + 3.2.0 + + + + com.example + demo-java-api + 1.0.0 + demo-java-api + Demo Java API for CI/CD testing + + + 17 + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-actuator + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + diff --git a/src/main/java/com/example/demo/DemoJavaApiApplication.java b/src/main/java/com/example/demo/DemoJavaApiApplication.java new file mode 100644 index 0000000..5d7670f --- /dev/null +++ b/src/main/java/com/example/demo/DemoJavaApiApplication.java @@ -0,0 +1,35 @@ +package com.example.demo; + +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; + +import java.time.Instant; +import java.util.HashMap; +import java.util.Map; + +@SpringBootApplication +@RestController +public class DemoJavaApiApplication { + + public static void main(String[] args) { + SpringApplication.run(DemoJavaApiApplication.class, args); + } + + @GetMapping("/") + public Map home() { + Map response = new HashMap<>(); + response.put("message", "Hello from Java API"); + response.put("version", "1.0.0"); + response.put("timestamp", Instant.now().toString()); + return response; + } + + @GetMapping("/healthz") + public Map health() { + Map response = new HashMap<>(); + response.put("status", "OK"); + return response; + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties new file mode 100644 index 0000000..cbd7014 --- /dev/null +++ b/src/main/resources/application.properties @@ -0,0 +1,5 @@ +server.port=8080 +spring.application.name=demo-java-api + +management.endpoints.web.exposure.include=health,info +management.endpoint.health.show-details=always