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
22 changes: 22 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Ignore git history
.git

# Ignore dependency directories
node_modules
venv
__pycache__

# Ignore compiled files
*.log
*.tmp
dist/

demo.ipynb
.github/

# Ignore sensitive files (Crucial!)
.env
secrets.json
Dockerfile
.dockerignore

316 changes: 316 additions & 0 deletions .github/workflow/docker-publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,316 @@
name: CI/CD - Docker Build & Publish

on:
push:
branches:
- main
- develop
tags:
- 'v*.*.*' # e.g., v1.0.0, v2.1.3
pull_request:
branches:
- main
- develop
workflow_dispatch:
inputs:
version:
description: 'Version tag (e.g., 1.0.0)'
required: false
type: string

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

jobs:
# Test & Quality Checks
test-and-quality:
name: Test & Code Quality
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.11", "3.12"]

services:
neo4j:
image: neo4j:5.12
env:
NEO4J_AUTH: neo4j/password
NEO4J_PLUGINS: '["apoc", "graph-data-science"]'
NEO4J_dbms_security_procedures_unrestricted: apoc.*
ports:
- 7474:7474
- 7687:7687
options: >-
--health-cmd "wget --no-verbose --tries=1 --spider http://localhost:7474 || exit 1"
--health-interval 5s
--health-timeout 3s
--health-retries 5

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
cache: 'pip'

- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt

- name: Wait for Neo4j to be ready
run: |
echo "Waiting for Neo4j to be ready..."
timeout 60 bash -c 'until curl -f http://localhost:7474; do sleep 2; done'
echo "Neo4j is ready!"

- name: Run unit tests
run: |
pytest tests/test_api.py -v --tb=short
env:
PYTHONPATH: .
NEO4J_URI: bolt://localhost:7687
NEO4J_USER: neo4j
NEO4J_PASSWORD: password
HF_TOKEN: ${{ secrets.HF_TOKEN }}

- name: Run integration tests
run: |
pytest tests/test_integration.py -v --tb=short
env:
PYTHONPATH: .
NEO4J_URI: bolt://localhost:7687
NEO4J_USER: neo4j
NEO4J_PASSWORD: password
HF_TOKEN: ${{ secrets.HF_TOKEN }}

- name: Run Pylint
run: |
pylint app --rcfile=.pylintrc
env:
PYTHONPATH: .
continue-on-error: false

- name: Check code formatting
run: |
pip install black
black --check app --line-length 120

# Build Docker Image
build:
name: Build Docker Image
runs-on: ubuntu-latest
needs: test-and-quality
if: github.event_name != 'pull_request'

outputs:
image-tag: ${{ steps.meta.outputs.tags }}
image-digest: ${{ steps.build.outputs.digest }}
version: ${{ steps.meta.outputs.version }}

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={{major}}
type=raw,value=latest,enable={{is_default_branch}}
type=sha,prefix={{branch}}-
type=raw,value=${{ github.event.inputs.version }},enable=${{ github.event.inputs.version != '' }}

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

- name: Image digest
run: echo "Image digest: ${{ steps.build.outputs.digest }}"

# Test Docker Image
test-docker-image:
name: Test Docker Image
runs-on: ubuntu-latest
needs: build
if: github.event_name != 'pull_request'

services:
neo4j:
image: neo4j:5.12
env:
NEO4J_AUTH: neo4j/password
NEO4J_PLUGINS: '["apoc", "graph-data-science"]'
NEO4J_dbms_security_procedures_unrestricted: apoc.*
ports:
- 7474:7474
- 7687:7687
options: >-
--health-cmd "wget --no-verbose --tries=1 --spider http://localhost:7474 || exit 1"
--health-interval 5s
--health-timeout 3s
--health-retries 5

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Wait for Neo4j to be ready
run: |
echo "Waiting for Neo4j to be ready..."
timeout 60 bash -c 'until curl -f http://localhost:7474; do sleep 2; done'
echo "Neo4j is ready!"

- name: Pull Docker image
run: |
echo ${{ secrets.GITHUB_TOKEN }} | docker login ${{ env.REGISTRY }} -u ${{ github.actor }} --password-stdin
docker pull ${{ needs.build.outputs.image-tag }}

- name: Test Docker image imports
run: |
docker run --rm --network host \
-e NEO4J_URI=bolt://localhost:7687 \
-e NEO4J_USER=neo4j \
-e NEO4J_PASSWORD=password \
${{ needs.build.outputs.image-tag }} \
python -c "import app; print('✅ App imports successfully')"

- name: Test Docker image health
run: |
docker run -d --name test-api --network host \
-e NEO4J_URI=bolt://localhost:7687 \
-e NEO4J_USER=neo4j \
-e NEO4J_PASSWORD=password \
-p 8001:8000 \
${{ needs.build.outputs.image-tag }}

sleep 10

# Test health endpoint
curl -f http://localhost:8001/ || exit 1
echo "✅ API is responding"

docker stop test-api
docker rm test-api

# Security Scan
security-scan:
name: Security Scan
runs-on: ubuntu-latest
needs: build
if: github.event_name != 'pull_request'

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@master
with:
image-ref: ${{ needs.build.outputs.image-tag }}
format: 'sarif'
output: 'trivy-results.sarif'

- name: Upload Trivy results to GitHub Security
uses: github/codeql-action/upload-sarif@v3
if: always()
with:
sarif_file: 'trivy-results.sarif'

# Publish to Docker Hub (Optional)
publish-dockerhub:
name: Publish to Docker Hub
runs-on: ubuntu-latest
needs: [build, test-docker-image]
if: |
github.event_name != 'pull_request' &&
github.ref == 'refs/heads/main' &&
secrets.DOCKERHUB_USERNAME != ''

steps:
- name: Checkout code
uses: actions/checkout@v4

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

- name: Log in to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}

- name: Extract metadata for Docker Hub
id: meta-dockerhub
uses: docker/metadata-action@v5
with:
images: ${{ secrets.DOCKERHUB_USERNAME }}/graph-project
tags: |
type=ref,event=branch
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=raw,value=latest,enable={{is_default_branch}}

- name: Build and push to Docker Hub
uses: docker/build-push-action@v5
with:
context: .
file: ./Dockerfile
push: true
tags: ${{ steps.meta-dockerhub.outputs.tags }}
cache-from: type=gha
cache-to: type=gha,mode=max

# Release Summary
release-summary:
name: Release Summary
runs-on: ubuntu-latest
needs: [build, test-docker-image, security-scan]
if: |
github.event_name != 'pull_request' &&
(github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v'))

steps:
- name: Create Release Summary
run: |
echo "## 🚀 Docker Image Published" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "### Image Details" >> $GITHUB_STEP_SUMMARY
echo "- **Registry**: ${{ env.REGISTRY }}" >> $GITHUB_STEP_SUMMARY
echo "- **Image**: \`${{ needs.build.outputs.image-tag }}\`" >> $GITHUB_STEP_SUMMARY
echo "- **Digest**: \`${{ needs.build.outputs.image-digest }}\`" >> $GITHUB_STEP_SUMMARY
echo "- **Version**: \`${{ needs.build.outputs.version }}\`" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "### Pull Command" >> $GITHUB_STEP_SUMMARY
echo "\`\`\`bash" >> $GITHUB_STEP_SUMMARY
echo "docker pull ${{ needs.build.outputs.image-tag }}" >> $GITHUB_STEP_SUMMARY
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
Loading