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
61 changes: 59 additions & 2 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,26 @@ jobs:
uses: ./.github/workflows/code-quality.yml
secrets: inherit

run-security:
needs: [run-code-quality]
uses: ./.github/workflows/security.yml
secrets: inherit
permissions:
contents: read
security-events: write # Required for SARIF uploads to GitHub Security tab

run-docker-compose-validate:
needs: [run-code-quality]
uses: ./.github/workflows/docker-compose-validate.yml
secrets: inherit

run-docker-validate:
needs: [list-sub-projects, run-code-quality]
uses: ./.github/workflows/docker-validate.yml
secrets: inherit
with:
simple-matrix: ${{ needs.list-sub-projects.outputs.simple-matrix }}

# Test workflow runs all service tests in parallel
# Each service has its own job that runs independently
run-tests:
Expand All @@ -50,9 +70,46 @@ jobs:
pull-requests: write # Required for coverage report comments
statuses: write # Required for coverage status creation (only used on PRs)

# ============================================================================
# AGGREGATE RESULTS - Waits for all parallel workflows to complete
# ============================================================================
aggregate-results:
runs-on: ubuntu-latest
needs:
- run-code-quality
- run-tests
- run-e2e-tests
- run-security
- run-docker-compose-validate
- run-docker-validate
if: always()

steps:
- name: 📊 Check all pipeline results
run: |
echo "Pipeline Results Summary:"
echo " Code Quality: ${{ needs.run-code-quality.result }}"
echo " Tests: ${{ needs.run-tests.result }}"
echo " E2E Tests: ${{ needs.run-e2e-tests.result }}"
echo " Security: ${{ needs.run-security.result }}"
echo " Docker Compose Validate: ${{ needs.run-docker-compose-validate.result }}"
echo " Docker Validate: ${{ needs.run-docker-validate.result }}"

if [[ "${{ needs.run-code-quality.result }}" == "failure" ]] || \
[[ "${{ needs.run-tests.result }}" == "failure" ]] || \
[[ "${{ needs.run-e2e-tests.result }}" == "failure" ]] || \
[[ "${{ needs.run-security.result }}" == "failure" ]] || \
[[ "${{ needs.run-docker-compose-validate.result }}" == "failure" ]] || \
[[ "${{ needs.run-docker-validate.result }}" == "failure" ]]; then
echo "❌ One or more pipeline workflows failed"
exit 1
fi

echo "✅ All pipeline workflows passed!"

build-discogsography:
# Wait for both test workflows to complete before building
needs: [list-sub-projects, run-tests, run-e2e-tests]
# Wait for aggregate-results (which ensures all quality gates pass) before building
needs: [list-sub-projects, aggregate-results]

runs-on: ubuntu-latest
timeout-minutes: 30
Expand Down
25 changes: 0 additions & 25 deletions .github/workflows/code-quality.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,32 +4,7 @@
name: Code Quality

on:
workflow_dispatch:
workflow_call:
push:
branches:
- main
paths:
- "**/*.py"
- "**/pyproject.toml"
- "**/uv.lock"
- "**/Dockerfile"
- "extractor/**/*.rs"
- "extractor/**/Cargo.toml"
- ".github/workflows/code-quality.yml"
- ".pre-commit-config.yaml"
pull_request:
branches:
- main
paths:
- "**/*.py"
- "**/pyproject.toml"
- "**/uv.lock"
- "**/Dockerfile"
- "extractor/**/*.rs"
- "extractor/**/Cargo.toml"
- ".github/workflows/code-quality.yml"
- ".pre-commit-config.yaml"

env:
CI: true
Expand Down
115 changes: 115 additions & 0 deletions .github/workflows/docker-compose-validate.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
---
# This workflow validates docker-compose configuration including syntax,
# service definitions, dependencies, and security best practices.
# It is called by the build.yml workflow.
name: Docker Compose Validate

on:
workflow_call:

env:
CI: true
COMPOSE_VERSION: "v2.32.0"

permissions:
contents: read

jobs:
validate-compose:
runs-on: ubuntu-latest
timeout-minutes: 10

steps:
- name: 🔀 Checkout repository
uses: actions/checkout@v6

- name: 🔧 Install docker-compose
uses: alexellis/arkade-get@1eef818e467c387d3f50cfe0d2c565d1cbe82b03 # master
with:
docker-compose: ${{ env.COMPOSE_VERSION }}

- name: 📋 Validate docker-compose syntax
run: |
docker-compose config --quiet
echo "✅ docker-compose.yml is valid"

if [ -f docker-compose.prod.yml ]; then
docker-compose -f docker-compose.yml -f docker-compose.prod.yml config --quiet
echo "✅ docker-compose.prod.yml overlay is valid"
fi

- name: 🔍 Check docker-compose services
run: |
services=$(docker-compose config --services | sort | tr "\n" " " | sed "s/ $//")
expected="api curator dashboard explore extractor graphinator neo4j postgres rabbitmq redis schema-init tableinator"

if [ "$services" != "$expected" ]; then
echo "❌ Service mismatch!"
echo "Expected: $expected"
echo "Got: $services"
exit 1
fi
echo "✅ All expected services are defined"

- name: 🔒 Validate service dependencies
run: |
# Check that services have correct dependencies
deps=$(docker-compose config | yq eval '.services.dashboard.depends_on | keys | sort | join(" ")' -)
if [ "$deps" != "neo4j postgres rabbitmq redis schema-init" ]; then
echo "❌ Dashboard should depend on neo4j, postgres, rabbitmq, redis, and schema-init"
exit 1
fi

deps=$(docker-compose config | yq eval '.services.extractor.depends_on | keys | .[]' -)
if [ "$deps" != "rabbitmq" ]; then
echo "❌ Extractor should only depend on rabbitmq"
exit 1
fi

deps=$(docker-compose config | yq eval '.services.graphinator.depends_on | keys | sort | join(" ")' -)
if [ "$deps" != "neo4j rabbitmq schema-init" ]; then
echo "❌ Graphinator should depend on neo4j, rabbitmq, and schema-init"
exit 1
fi

deps=$(docker-compose config | yq eval '.services.explore.depends_on | keys | sort | join(" ")' -)
if [ "$deps" != "neo4j schema-init" ]; then
echo "❌ Explore should depend on neo4j and schema-init"
exit 1
fi

deps=$(docker-compose config | yq eval '.services.tableinator.depends_on | keys | sort | join(" ")' -)
if [ "$deps" != "postgres rabbitmq schema-init" ]; then
echo "❌ Tableinator should depend on postgres, rabbitmq, and schema-init"
exit 1
fi

deps=$(docker-compose config | yq eval '.services.schema-init.depends_on | keys | sort | join(" ")' -)
if [ "$deps" != "neo4j postgres" ]; then
echo "❌ Schema-init should depend on neo4j and postgres"
exit 1
fi

echo "✅ Service dependencies are correct"

- name: 🛡️ Check for security best practices
run: |
# Check that services run as non-root user
for service in dashboard explore extractor graphinator schema-init tableinator; do
user=$(docker-compose config | yq eval ".services.$service.user" -)
if [ "$user" != "1000:1000" ]; then
echo "❌ $service should run as user 1000:1000"
exit 1
fi
done
echo "✅ All services run as non-root user"

# Check security options
for service in dashboard explore extractor graphinator schema-init tableinator; do
security_opt=$(docker-compose config | yq eval ".services.$service.security_opt[]" - | grep "no-new-privileges:true" || true)
if [ -z "$security_opt" ]; then
echo "❌ $service should have no-new-privileges security option"
exit 1
fi
done
echo "✅ Security options are properly set"
130 changes: 7 additions & 123 deletions .github/workflows/docker-validate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,48 +2,31 @@
name: Docker Validate

on:
workflow_dispatch:
push:
branches:
- main
paths:
- "**/Dockerfile"
- "docker-compose*.yml"
- ".dockerignore"
- ".github/workflows/docker-validate.yml"
pull_request:
branches:
- main
paths:
- "**/Dockerfile"
- "docker-compose*.yml"
- ".dockerignore"
- ".github/workflows/docker-validate.yml"
workflow_call:
inputs:
simple-matrix:
description: "Simple JSON array of sub-project names"
required: true
type: string

concurrency:
group: docker-validate-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

env:
COMPOSE_VERSION: "v2.32.0"
PYTHON_VERSION: "3.13"

permissions:
contents: read

jobs:
list-sub-projects:
uses: ./.github/workflows/list-sub-projects.yml

validate-dockerfiles:
needs: list-sub-projects

runs-on: ubuntu-latest
timeout-minutes: 10

strategy:
matrix:
sub-project: ${{ fromJson(needs.list-sub-projects.outputs.simple-matrix) }}
sub-project: ${{ fromJson(inputs.simple-matrix) }}

steps:
- name: 🔀 Checkout repository
Expand All @@ -63,102 +46,3 @@ jobs:
echo "🔨 Testing build for ${{ matrix.sub-project }}..."
docker build --build-arg PYTHON_VERSION=${{ env.PYTHON_VERSION }} -f ${{ matrix.sub-project }}/Dockerfile . --target builder
echo "✅ ${{ matrix.sub-project }} builder stage built successfully"

validate-compose:
runs-on: ubuntu-latest
timeout-minutes: 10

steps:
- name: 🔀 Checkout repository
uses: actions/checkout@v6

- name: 🔧 Install docker-compose
uses: alexellis/arkade-get@1eef818e467c387d3f50cfe0d2c565d1cbe82b03 # master
with:
docker-compose: latest

- name: 📋 Validate docker-compose syntax
run: |
docker-compose config --quiet
echo "✅ docker-compose.yml is valid"

if [ -f docker-compose.prod.yml ]; then
docker-compose -f docker-compose.yml -f docker-compose.prod.yml config --quiet
echo "✅ docker-compose.prod.yml overlay is valid"
fi

- name: 🔍 Check docker-compose services
run: |
services=$(docker-compose config --services | sort | tr "\n" " " | sed "s/ $//")
expected="api curator dashboard explore extractor graphinator neo4j postgres rabbitmq redis schema-init tableinator"

if [ "$services" != "$expected" ]; then
echo "❌ Service mismatch!"
echo "Expected: $expected"
echo "Got: $services"
exit 1
fi
echo "✅ All expected services are defined"

- name: 🔒 Validate service dependencies
run: |
# Check that services have correct dependencies
deps=$(docker-compose config | yq eval '.services.dashboard.depends_on | keys | sort | join(" ")' -)
if [ "$deps" != "neo4j postgres rabbitmq redis schema-init" ]; then
echo "❌ Dashboard should depend on neo4j, postgres, rabbitmq, redis, and schema-init"
exit 1
fi

deps=$(docker-compose config | yq eval '.services.extractor.depends_on | keys | .[]' -)
if [ "$deps" != "rabbitmq" ]; then
echo "❌ Extractor should only depend on rabbitmq"
exit 1
fi

deps=$(docker-compose config | yq eval '.services.graphinator.depends_on | keys | sort | join(" ")' -)
if [ "$deps" != "neo4j rabbitmq schema-init" ]; then
echo "❌ Graphinator should depend on neo4j, rabbitmq, and schema-init"
exit 1
fi

deps=$(docker-compose config | yq eval '.services.explore.depends_on | keys | sort | join(" ")' -)
if [ "$deps" != "neo4j schema-init" ]; then
echo "❌ Explore should depend on neo4j and schema-init"
exit 1
fi

deps=$(docker-compose config | yq eval '.services.tableinator.depends_on | keys | sort | join(" ")' -)
if [ "$deps" != "postgres rabbitmq schema-init" ]; then
echo "❌ Tableinator should depend on postgres, rabbitmq, and schema-init"
exit 1
fi

deps=$(docker-compose config | yq eval '.services.schema-init.depends_on | keys | sort | join(" ")' -)
if [ "$deps" != "neo4j postgres" ]; then
echo "❌ Schema-init should depend on neo4j and postgres"
exit 1
fi

echo "✅ Service dependencies are correct"

- name: 🛡️ Check for security best practices
run: |
# Check that services run as non-root user
for service in dashboard explore extractor graphinator schema-init tableinator; do
user=$(docker-compose config | yq eval ".services.$service.user" -)
if [ "$user" != "1000:1000" ]; then
echo "❌ $service should run as user 1000:1000"
exit 1
fi
done
echo "✅ All services run as non-root user"

# Check security options
for service in dashboard explore extractor graphinator schema-init tableinator; do
security_opt=$(docker-compose config | yq eval ".services.$service.security_opt[]" - | grep "no-new-privileges:true" || true)
if [ -z "$security_opt" ]; then
echo "❌ $service should have no-new-privileges security option"
exit 1
fi
done
echo "✅ Security options are properly set"
3 changes: 1 addition & 2 deletions .github/workflows/e2e-test.yml
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
---
# This workflow runs all E2E tests including desktop browsers and mobile device emulation.
# It can be called from other workflows or triggered directly on service changes.
# It is called by the build.yml workflow.
name: E2E Test

on:
workflow_dispatch:
workflow_call:

concurrency:
Expand Down
Loading
Loading