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
169 changes: 169 additions & 0 deletions .github/workflows/ci-cd.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
name: CI/CD Pipeline

on:
push:
branches: [ main, develop ]
tags: [ 'v*' ]
pull_request:
branches: [ main ]

env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}

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

- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'

- name: Cache Gradle packages
uses: actions/cache@v4
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: |
${{ runner.os }}-gradle-

- name: Make gradlew executable
run: chmod +x ./gradlew

- name: Run tests
run: ./gradlew test jacocoTestReport

- name: Check test coverage
run: ./gradlew jacocoTestCoverageVerification

- name: Upload test results
uses: actions/upload-artifact@v4
if: always()
with:
name: test-results
path: |
build/reports/tests/
build/reports/jacoco/

build-and-push:
needs: test
runs-on: ubuntu-latest
outputs:
image-tag: ${{ steps.meta.outputs.tags }}
image-digest: ${{ steps.build.outputs.digest }}
steps:
- name: Checkout code
uses: actions/checkout@v4

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

- name: Log in to Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern=1.0.{{patch}}
type=raw,value=latest,enable={{is_default_branch}}

- name: Build and push Docker image
id: build
uses: docker/build-push-action@v5
with:
context: .
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max

deploy:
needs: build-and-push
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v')
steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Set up Helm
uses: azure/setup-helm@v4
with:
version: '3.13.0'

- name: Set up kubectl
uses: azure/setup-kubectl@v4
with:
version: '1.28.0'

- name: Configure kubectl for IKS
run: |
echo "${{ secrets.KUBECONFIG }}" | base64 -d > kubeconfig
export KUBECONFIG=kubeconfig
kubectl config current-context

- name: Deploy to Kubernetes
env:
KUBECONFIG: kubeconfig
SENDGRID_API_KEY: ${{ secrets.SENDGRID_API_KEY }}
KAFKA_BOOTSTRAP_SERVERS: ${{ secrets.KAFKA_BOOTSTRAP_SERVERS }}
run: |
# Extract version from tag or use latest
if [[ $GITHUB_REF == refs/tags/v* ]]; then
VERSION=${GITHUB_REF#refs/tags/v}
else
VERSION="latest"
fi

# Deploy using Helm
helm upgrade --install kafka-sendgrid ./helm/kafka-sendgrid \
--namespace kafka-sendgrid \
--create-namespace \
--set image.tag=${VERSION} \
--set secrets.sendgridApiKey=${SENDGRID_API_KEY} \
--set config.kafka.bootstrapServers=${KAFKA_BOOTSTRAP_SERVERS} \
--wait \
--timeout=300s

- name: Verify deployment
env:
KUBECONFIG: kubeconfig
run: |
kubectl get pods -n kafka-sendgrid
kubectl rollout status deployment/kafka-sendgrid -n kafka-sendgrid

security-scan:
needs: build-and-push
runs-on: ubuntu-latest
steps:
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@master
with:
image-ref: ${{ needs.build-and-push.outputs.image-tag }}
format: 'sarif'
output: 'trivy-results.sarif'

- name: Upload Trivy scan results to GitHub Security tab
uses: github/codeql-action/upload-sarif@v3
if: always()
with:
sarif_file: 'trivy-results.sarif'
46 changes: 46 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Gradle
.gradle/
build/
!gradle/wrapper/gradle-wrapper.jar

# Java
*.class
*.jar
*.war
*.nar
*.ear
*.zip
*.tar.gz
*.rar

# IDE
.idea/
*.iws
*.iml
*.ipr
out/
.vscode/

# OS
.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db

# Logs
*.log

# Test Reports
/reports/

# Temporary files
*.tmp
*.swp
*.bak

# Environment variables
.env
.env.local
52 changes: 52 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# Build stage
FROM gradle:8.11.1-jdk17-alpine AS build

# Set working directory
WORKDIR /app

# Copy gradle files
COPY build.gradle .
COPY gradle ./gradle
COPY gradlew .
COPY settings.gradle* ./

# Copy source code
COPY src ./src

# Build the application
RUN ./gradlew build --no-daemon -x test

# Runtime stage - using Java 17 Alpine 3.21
FROM eclipse-temurin:17-jre-alpine

# Install dumb-init for proper signal handling
RUN apk add --no-cache dumb-init

# Create application user
RUN addgroup -g 1001 -S appgroup && \
adduser -u 1001 -S appuser -G appgroup

# Set working directory
WORKDIR /app

# Copy the built JAR from build stage
COPY --from=build /app/build/libs/kafka-sendgrid-*.jar app.jar

# Change ownership of the app directory
RUN chown -R appuser:appgroup /app

# Switch to non-root user
USER appuser

# Expose the port
EXPOSE 8080

# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD wget --no-verbose --tries=1 --spider http://localhost:8080/actuator/health || exit 1

# Use dumb-init to properly handle signals
ENTRYPOINT ["dumb-init", "--"]

# Run the application
CMD ["java", "-Djava.security.egd=file:/dev/./urandom", "-jar", "app.jar"]
Loading
Loading