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
80 changes: 56 additions & 24 deletions .github/workflows/Publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,41 +3,50 @@ name: Publish
on:
push:
branches:
- 'main'
- "main"

workflow_dispatch:
inputs:
force:
description: 'Force publish all components'
description: "Force publish all components"
required: false
type: boolean
default: false
force_components:
description: "Force publish specific components (comma-separated: backend, frontend, healthcheck)"
required: false
type: string
default: ""

jobs:

paths-filter:
runs-on: ubuntu-24.04

outputs:
backend: ${{ steps.filter.outputs.backend }}
frontend: ${{ steps.filter.outputs.frontend }}
backend: ${{ steps.filter.outputs.backend || 'false'}}
frontend: ${{ steps.filter.outputs.frontend || 'false'}}
healthcheck: ${{ steps.filter.outputs.healthcheck || 'false'}}

steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v5

- uses: dorny/paths-filter@v3
id: filter
with:
filters: |
backend:
- 'backend/**'
frontend:
- 'frontend-ui/**'
- uses: dorny/paths-filter@v3
id: filter
if: github.event_name == 'push'
with:
filters: |
backend:
- 'backend/**'
frontend:
- 'frontend/**'
healthcheck:
- 'healthcheck/**'

backend:
name: Backend build
runs-on: ubuntu-24.04
needs: paths-filter
if: ${{ needs.paths-filter.outputs.backend == 'true' || inputs.force }}
if: ${{ needs.paths-filter.outputs.backend == 'true' || inputs.force == true || contains(inputs.force_components, 'backend') }}
steps:
- name: Retrieve source code
uses: actions/checkout@v5
Expand All @@ -51,8 +60,7 @@ jobs:
context: backend
dockerfile: Dockerfile-mill
registries: ghcr.io
credentials:
GHCRIO_USERNAME=${{ secrets.GHCR_USERNAME }}
credentials: GHCRIO_USERNAME=${{ secrets.GHCR_USERNAME }}
GHCRIO_TOKEN=${{ secrets.GHCR_TOKEN }}

- name: Build and publish Docker Image for backend shuttle
Expand All @@ -64,8 +72,7 @@ jobs:
context: backend
dockerfile: Dockerfile-shuttle
registries: ghcr.io
credentials:
GHCRIO_USERNAME=${{ secrets.GHCR_USERNAME }}
credentials: GHCRIO_USERNAME=${{ secrets.GHCR_USERNAME }}
GHCRIO_TOKEN=${{ secrets.GHCR_TOKEN }}

- name: Build and publish Docker Image for backend API
Expand All @@ -77,8 +84,7 @@ jobs:
context: backend
dockerfile: Dockerfile-api
registries: ghcr.io
credentials:
GHCRIO_USERNAME=${{ secrets.GHCR_USERNAME }}
credentials: GHCRIO_USERNAME=${{ secrets.GHCR_USERNAME }}
GHCRIO_TOKEN=${{ secrets.GHCR_TOKEN }}

# mill should be first since it deploys the alembic migration
Expand Down Expand Up @@ -107,7 +113,7 @@ jobs:
name: Frontend build
runs-on: ubuntu-24.04
needs: paths-filter
if: ${{ needs.paths-filter.outputs.frontend == 'true' || inputs.force }}
if: ${{ needs.paths-filter.outputs.frontend == 'true' || inputs.force == true || contains(inputs.force_components, 'frontend') }}
steps:
- name: Retrieve source code
uses: actions/checkout@v5
Expand All @@ -120,8 +126,7 @@ jobs:
restrict-to: openzim/cms
context: frontend
registries: ghcr.io
credentials:
GHCRIO_USERNAME=${{ secrets.GHCR_USERNAME }}
credentials: GHCRIO_USERNAME=${{ secrets.GHCR_USERNAME }}
GHCRIO_TOKEN=${{ secrets.GHCR_TOKEN }}

- name: Deploy CMS UI changes to cms.openzim.org
Expand All @@ -130,3 +135,30 @@ jobs:
KUBE_CONFIG: ${{ secrets.KUBE_CONFIG }}
with:
args: rollout restart deployments ui-deployment -n cms

healthcheck:
name: Healthcheck build
runs-on: ubuntu-24.04
needs: paths-filter
if: ${{ needs.paths-filter.outputs.healthcheck == 'true' || inputs.force == true || contains(inputs.force_components, 'healthcheck') }}
steps:
- name: Retrieve source code
uses: actions/checkout@v6

- name: Build and publish Docker Image
uses: openzim/docker-publish-action@v10
with:
image-name: openzim/cms-healthcheck
on-master: latest
restrict-to: openzim/cms
context: healthcheck
registries: ghcr.io
credentials: GHCRIO_USERNAME=${{ secrets.GHCR_USERNAME }}
GHCRIO_TOKEN=${{ secrets.GHCR_TOKEN }}

- name: Deploy Healthcheck changes to cms.openzim.org
uses: actions-hub/kubectl@master
env:
KUBE_CONFIG: ${{ secrets.KUBE_CONFIG }}
with:
args: rollout restart deployments status-deployment -n cms
41 changes: 41 additions & 0 deletions .github/workflows/healthcheck-QA.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
name: Healthcheck QA

on:
pull_request:
paths:
- "healthcheck/**"
push:
paths:
- "healthcheck/**"
branches:
- main

jobs:
check-qa:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v6

- name: Set up Python
uses: actions/setup-python@v6
with:
python-version-file: healthcheck/pyproject.toml
architecture: x64

- name: Install dependencies (and project)
working-directory: healthcheck
run: |
pip install -U pip
pip install -e .[lint,scripts,test,check]

- name: Check black formatting
working-directory: healthcheck
run: inv lint-black

- name: Check ruff
working-directory: healthcheck
run: inv lint-ruff

- name: Check pyright
working-directory: healthcheck
run: inv check-pyright
48 changes: 48 additions & 0 deletions .github/workflows/healthcheck-Tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
name: Healthcheck Tests

on:
pull_request:
paths:
- "healthcheck/**"
push:
paths:
- "healthcheck/**"
branches:
- main

jobs:
run-tests:
runs-on: ubuntu-24.04
steps:
- name: Retrieve source code
uses: actions/checkout@v6

- name: Set up Python
uses: actions/setup-python@v6
with:
python-version-file: healthcheck/pyproject.toml
architecture: x64

- name: Build healthcheck Docker image
working-directory: healthcheck
run: docker build -t cms-healthcheck:test .

- name: Run healthcheck container
run: |
docker run -d --name cms-healthcheck-test \
-e CMS_API_URL=http://localhost:8001 \
-e CMS_FRONTEND_URL=http://localhost:8002 \
-e CMS_USERNAME=admin \
-e CMS_PASSWORD=admin_pass \
-e CMS_DATABASE_URL=postgresql+psycopg://cms:cmspass@localhost:5432/cmstest \
-p 8000:80 \
cms-healthcheck:test
# wait for container to be ready
for i in {1..10}; do
if curl -s http://localhost:8000 > /dev/null; then
echo "Healthcheck is up!"
break
fi
echo "Waiting for Healthcheck..."
sleep 3
done
4 changes: 2 additions & 2 deletions backend/src/cms_backend/api/routes/books.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ class BooksGetSchema(BaseModel):
needs_processing: bool | None = None
has_error: bool | None = None
needs_file_operation: bool | None = None
location_kind: NotEmptyString | None = None
location_kinds: list[NotEmptyString] | None = None


@router.get("")
Expand All @@ -48,7 +48,7 @@ def get_books(
needs_processing=params.needs_processing,
has_error=params.has_error,
needs_file_operation=params.needs_file_operation,
location_kind=params.location_kind,
location_kinds=params.location_kinds,
)

return ListResponse[BookLightSchema](
Expand Down
6 changes: 3 additions & 3 deletions backend/src/cms_backend/db/books.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ def get_books(
needs_processing: bool | None = None,
has_error: bool | None = None,
needs_file_operation: bool | None = None,
location_kind: str | None = None,
location_kinds: list[str] | None = None,
) -> ListResult[BookLightSchema]:
"""Get a list of books"""

Expand Down Expand Up @@ -76,8 +76,8 @@ def get_books(
if needs_file_operation is not None:
stmt = stmt.where(Book.needs_file_operation == needs_file_operation)

if location_kind is not None:
stmt = stmt.where(Book.location_kind == location_kind)
if location_kinds is not None:
stmt = stmt.where(Book.location_kind.in_(location_kinds))

return ListResult[BookLightSchema](
nb_records=count_from_stmt(session, stmt),
Expand Down
33 changes: 33 additions & 0 deletions dev/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -135,5 +135,38 @@ services:
depends_on:
api:
condition: service_healthy

healthcheck:
build: ../healthcheck
container_name: cms_healthcheck
ports:
- 127.0.0.1:37604:80
depends_on:
api:
condition: service_healthy
environment:
CMS_DATABASE_URL: postgresql+psycopg://cms:cmspass@postgresdb:5432/cms
CMS_FRONTEND_URL: http://frontend
CMS_USERNAME: cms
CMS_PASSWORD: cms_pass
CMS_API_URL: http://api/v1
volumes:
- ../healthcheck/src/healthcheck:/usr/local/lib/python3.13/site-packages/healthcheck
command:
- uvicorn
- healthcheck.main:app
- --host
- "0.0.0.0"
- --port
- "80"
- --proxy-headers
- --forwarded-allow-ips
- "*"
- --reload
- --reload-dir
- /usr/local/lib/python3.13/site-packages/healthcheck
profiles:
- healthcheck

volumes:
pg_data_cms:
4 changes: 2 additions & 2 deletions frontend/src/stores/book.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export const useBookStore = defineStore('book', () => {
skip: number,
has_title: boolean | undefined = undefined,
id: string | undefined = undefined,
location_kind: string | undefined = undefined,
location_kinds: string[] | undefined = undefined,
) => {
const service = await authStore.getApiService('books')
// filter out undefined values from params
Expand All @@ -56,7 +56,7 @@ export const useBookStore = defineStore('book', () => {
skip,
has_title,
id,
location_kind,
location_kinds,
}).filter(
([name, value]) => !!value || (!['limit', 'skip'].includes(name) && value !== undefined),
),
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/views/InboxView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ async function loadData(limit: number, skip: number, tab?: string, hideLoading:
skip,
false,
bookFilters.value.id || undefined,
bookFilters.value.location_kind || undefined,
bookFilters.value.location_kind ? [bookFilters.value.location_kind] : undefined,
)
books.value = bookStore.books
errors.value = bookStore.errors
Expand Down
20 changes: 20 additions & 0 deletions healthcheck/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
FROM python:3.13-slim-bookworm
LABEL org.opencontainers.image.source https://github.com/openzim/cms
LABEL cms true

# install Python dependencies first (since they change more rarely than src code)
COPY pyproject.toml README.md /app/
COPY src/healthcheck/__about__.py /app/src/healthcheck/__about__.py
RUN pip install --no-cache-dir /app

COPY src /app/src

RUN pip install --no-cache-dir /app \
&& rm -rf /app/src \
&& rm /app/pyproject.toml \
&& rm /app/README.md


EXPOSE 80

CMD ["uvicorn", "healthcheck.main:app", "--host", "0.0.0.0", "--port", "80"]
11 changes: 11 additions & 0 deletions healthcheck/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
## Healthcheck

This service monitors the status of CMS services and components and displays results as HTML

### Environment variables

- CMS_API_URL: CMS backend API URL.
- CMS_FRONTEND_URL: CMS frontend UI URL.
- CMS_USERNAME: Username to authenticate with on CMS.
- CMS_PASSWORD: Password of user to authenticate with on CMS.
- CMS_DATABASE_URL: CMS database connection URL, including the driver, user credentials, host and port e.g `postgresql+psycopg://cms:cmspass@postgresdb:5432/cms`.
Loading