Skip to content

Commit 1461f07

Browse files
committed
feat: Enhance CI/CD workflows with frontend setup and TestPyPI publishing
1 parent 3aa3dd3 commit 1461f07

17 files changed

Lines changed: 403 additions & 148 deletions

File tree

.github/workflows/build-images.yml

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,16 @@ jobs:
2020
run: |
2121
echo "version=$(cat .version)" >> $GITHUB_OUTPUT
2222
23+
- name: Setup Node (for frontend context)
24+
uses: actions/setup-node@v4
25+
with:
26+
node-version: '20'
27+
28+
- name: Install frontend deps (workspace cache for Docker layer cache)
29+
working-directory: services/frontend
30+
run: |
31+
npm ci || true
32+
2333
- name: Login to GHCR
2434
uses: docker/login-action@v3
2535
with:
@@ -38,19 +48,32 @@ jobs:
3848
docker buildx build --push -t ghcr.io/stackitcloud/rag-template/admin-backend:${VERSION} -f services/admin-backend/Dockerfile .
3949
docker buildx build --push -t ghcr.io/stackitcloud/rag-template/document-extractor:${VERSION} -f services/document-extractor/Dockerfile .
4050
docker buildx build --push -t ghcr.io/stackitcloud/rag-template/mcp-server:${VERSION} -f services/mcp-server/Dockerfile .
51+
# Frontend apps (chat-app and admin-app)
52+
docker buildx build --push -t ghcr.io/stackitcloud/rag-template/frontend:${VERSION} -f services/frontend/apps/chat-app/Dockerfile .
53+
docker buildx build --push -t ghcr.io/stackitcloud/rag-template/admin-frontend:${VERSION} -f services/frontend/apps/admin-app/Dockerfile .
54+
55+
- name: Install jq
56+
run: sudo apt-get update && sudo apt-get install -y jq
4157

4258
- name: Capture image digests
4359
run: |
4460
VERSION="${{ steps.ver.outputs.version }}"
4561
: > image-digests.json
46-
for svc in rag-backend admin-backend document-extractor mcp-server; do
47-
ref="ghcr.io/stackitcloud/rag-template/${svc}:${VERSION}"
48-
digest=$(docker buildx imagetools inspect "$ref" --format '{{json .Manifest.Digest}}' | jq -r .)
62+
for ref in \
63+
ghcr.io/stackitcloud/rag-template/rag-backend:${VERSION} \
64+
ghcr.io/stackitcloud/rag-template/admin-backend:${VERSION} \
65+
ghcr.io/stackitcloud/rag-template/document-extractor:${VERSION} \
66+
ghcr.io/stackitcloud/rag-template/mcp-server:${VERSION} \
67+
ghcr.io/stackitcloud/rag-template/frontend:${VERSION} \
68+
ghcr.io/stackitcloud/rag-template/admin-frontend:${VERSION}
69+
do
70+
svc=$(basename "${ref%:*}")
71+
digest=$(docker buildx imagetools inspect "$ref" --format '{{json .Manifest.Digest}}' | jq -r . || true)
4972
tmp=$(mktemp)
5073
jq --arg s "$svc" --arg t "$VERSION" --arg d "$digest" \
5174
'.[$s] = {"tag": $t, "digest": $d}' \
52-
image-digests.json > "$tmp"
53-
mv "$tmp" image-digests.json
75+
image-digests.json > "$tmp" || echo '{}' > image-digests.json
76+
mv "$tmp" image-digests.json || true
5477
done
5578
5679
- name: Upload digests

.github/workflows/prepare-release.yml

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -53,14 +53,6 @@ jobs:
5353
run: |
5454
pip install poetry==2.1.3
5555
56-
- name: Re-lock services
57-
run: |
58-
set -e
59-
for svc in services/rag-backend services/admin-backend services/document-extractor; do
60-
echo "Locking $svc"
61-
(cd "$svc" && poetry lock)
62-
done
63-
6456
- name: Update Helm chart versions
6557
run: |
6658
pip install pyyaml packaging
@@ -74,13 +66,11 @@ jobs:
7466
body: |
7567
Prepare release ${{ steps.semrel.outputs.version }}
7668
- bump libs and service pins
77-
- update poetry.lock
7869
- update Helm chart appVersion and bump patch
7970
- add .version
8071
commit-message: "chore(release): prepare ${{ steps.semrel.outputs.version }}"
8172
add-paths: |
8273
.version
8374
libs/**/pyproject.toml
8475
services/**/pyproject.toml
85-
services/**/poetry.lock
8676
infrastructure/**/Chart.yaml

.github/workflows/publish-libs-on-merge.yml

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,14 +32,18 @@ jobs:
3232
run: |
3333
pip install poetry==2.1.3
3434
35-
- name: Build and publish libs to PyPI
35+
- name: Configure TestPyPI repository
36+
run: |
37+
poetry config repositories.testpypi https://test.pypi.org/legacy/
38+
39+
- name: Build and publish libs to TestPyPI
3640
env:
37-
POETRY_HTTP_BASIC_PYPI_USERNAME: __token__
38-
POETRY_HTTP_BASIC_PYPI_PASSWORD: ${{ secrets.PYPI_TOKEN }}
41+
POETRY_HTTP_BASIC_TESTPYPI_USERNAME: __token__
42+
POETRY_HTTP_BASIC_TESTPYPI_PASSWORD: ${{ secrets.TEST_PYPI_TOKEN }}
3943
run: |
4044
set -e
4145
for lib in libs/*; do
4246
[ -d "$lib" ] || continue
4347
echo "Publishing $lib"
44-
(cd "$lib" && poetry version "${{ steps.ver.outputs.version }}" && poetry build && poetry publish)
48+
(cd "$lib" && poetry version "${{ steps.ver.outputs.version }}" && poetry build && poetry publish -r testpypi)
4549
done

infrastructure/rag/templates/_admin_frontend_helpers.tpl

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,14 @@
11
{{- define "adminFrontend.fullImageName" -}}
22
{{- if .Values.adminFrontend.image -}}
33
{{- if .Values.adminFrontend.image.repository -}}
4-
{{- printf "%s:%s" .Values.adminFrontend.image.repository .Values.adminFrontend.image.tag | trimSuffix ":" }}
4+
{{- $repo := .Values.adminFrontend.image.repository -}}
5+
{{- $tag := default .Chart.AppVersion .Values.adminFrontend.image.tag -}}
6+
{{- $digest := default "" .Values.adminFrontend.image.digest -}}
7+
{{- if $digest -}}
8+
{{- printf "%s@%s" $repo $digest -}}
9+
{{- else -}}
10+
{{- printf "%s:%s" $repo $tag -}}
11+
{{- end -}}
512
{{- else -}}
613
{{ required "A valid .Values.adminFrontend.image.repository entry required!" . }}
714
{{- end -}}

scripts/bump_chart_versions.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
#!/usr/bin/env python3
2+
import argparse
3+
import pathlib
4+
import sys
5+
6+
import yaml
7+
from packaging.version import Version
8+
9+
ROOT = pathlib.Path(__file__).resolve().parents[1]
10+
11+
12+
def bump_chart(chart_path: pathlib.Path, app_version: str):
13+
data = yaml.safe_load(chart_path.read_text())
14+
data['appVersion'] = str(app_version)
15+
cv = Version(str(data['version']))
16+
# bump patch
17+
bumped = Version(f"{cv.major}.{cv.minor}.{cv.micro + 1}")
18+
data['version'] = str(bumped)
19+
chart_path.write_text(yaml.safe_dump(data, sort_keys=False))
20+
21+
22+
def main():
23+
p = argparse.ArgumentParser()
24+
p.add_argument('--app-version', required=True)
25+
args = p.parse_args()
26+
27+
charts = list((ROOT / 'infrastructure').glob('*/Chart.yaml'))
28+
for ch in charts:
29+
bump_chart(ch, args.app_version)
30+
31+
32+
if __name__ == '__main__':
33+
sys.exit(main())

scripts/bump_pyproject_deps.py

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
#!/usr/bin/env python3
2+
import argparse
3+
import pathlib
4+
import sys
5+
from typing import Dict, Any
6+
7+
import tomlkit
8+
9+
ROOT = pathlib.Path(__file__).resolve().parents[1]
10+
11+
LIBS = [
12+
(ROOT / 'libs' / 'rag-core-lib' / 'pyproject.toml', 'tool.poetry.version'),
13+
(ROOT / 'libs' / 'rag-core-api' / 'pyproject.toml', 'tool.poetry.version'),
14+
(ROOT / 'libs' / 'admin-api-lib' / 'pyproject.toml', 'tool.poetry.version'),
15+
(ROOT / 'libs' / 'extractor-api-lib' / 'pyproject.toml', 'tool.poetry.version'),
16+
]
17+
18+
SERVICE_PINS = {
19+
ROOT / 'services' / 'rag-backend' / 'pyproject.toml': {
20+
'tool.poetry.group.prod.dependencies.rag-core-api': '=={v}',
21+
'tool.poetry.group.prod.dependencies.rag-core-lib': '=={v}',
22+
},
23+
ROOT / 'services' / 'admin-backend' / 'pyproject.toml': {
24+
'tool.poetry.group.prod.dependencies.admin-api-lib': '=={v}',
25+
'tool.poetry.group.prod.dependencies.rag-core-lib': '=={v}',
26+
},
27+
ROOT / 'services' / 'document-extractor' / 'pyproject.toml': {
28+
'tool.poetry.group.prod.dependencies.extractor-api-lib': '=={v}',
29+
},
30+
}
31+
32+
33+
def set_value(doc: tomlkit.TOMLDocument, dotted_path: str, value: Any):
34+
parts = dotted_path.split('.')
35+
ref: Dict[str, Any] = doc
36+
for p in parts[:-1]:
37+
if p not in ref or not isinstance(ref[p], dict):
38+
ref[p] = tomlkit.table()
39+
ref = ref[p]
40+
ref[parts[-1]] = value
41+
42+
43+
def bump(version: str):
44+
# 1) bump libs versions
45+
for file, dotted in LIBS:
46+
txt = file.read_text()
47+
doc = tomlkit.parse(txt)
48+
set_value(doc, dotted, version)
49+
file.write_text(tomlkit.dumps(doc))
50+
51+
# 2) bump service pins
52+
for file, mapping in SERVICE_PINS.items():
53+
txt = file.read_text()
54+
doc = tomlkit.parse(txt)
55+
for dotted, template in mapping.items():
56+
set_value(doc, dotted, template.format(v=version))
57+
file.write_text(tomlkit.dumps(doc))
58+
59+
60+
def main():
61+
ap = argparse.ArgumentParser()
62+
ap.add_argument('--version', required=True)
63+
args = ap.parse_args()
64+
bump(args.version)
65+
66+
67+
if __name__ == '__main__':
68+
sys.exit(main())

services/admin-backend/Dockerfile

Lines changed: 13 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,26 @@
1-
FROM --platform=linux/amd64 python:3.13-bookworm AS build
1+
FROM python:3.13-bookworm AS build
22

3-
ARG dev=0
43
ENV POETRY_VIRTUALENVS_PATH=/app/services/admin-backend/.venv
54
ENV VIRTUAL_ENV="${POETRY_VIRTUALENVS_PATH}"
65
ENV POETRY_VERSION=2.1.3
76

87
RUN DEBIAN_FRONTEND=noninteractive apt-get update \
9-
&& DEBIAN_FRONTEND=noninteractive apt-get install -y build-essential --no-install-recommends make && \
10-
python3 -m venv "${POETRY_VIRTUALENVS_PATH}" \
11-
&& $POETRY_VIRTUALENVS_PATH/bin/pip install "poetry==${POETRY_VERSION}"
8+
&& DEBIAN_FRONTEND=noninteractive apt-get install -y build-essential --no-install-recommends make \
9+
&& python3 -m venv "${POETRY_VIRTUALENVS_PATH}" \
10+
&& ${POETRY_VIRTUALENVS_PATH}/bin/pip install "poetry==${POETRY_VERSION}"
1211
ENV PATH="${POETRY_VIRTUALENVS_PATH}/bin:$PATH"
1312

14-
COPY libs/admin-api-lib/pyproject.toml libs/admin-api-lib/poetry.lock /app/libs/admin-api-lib/
15-
COPY libs/rag-core-lib/pyproject.toml libs/rag-core-lib/poetry.lock /app/libs/rag-core-lib/
16-
1713
WORKDIR /app/services/admin-backend
1814
COPY services/admin-backend/pyproject.toml services/admin-backend/poetry.lock ./
1915

20-
RUN mkdir log && chmod 700 log
21-
RUN touch /app/services/admin-backend/log/logfile.log && chmod 600 /app/services/admin-backend/log/logfile.log
16+
RUN mkdir -p log && chmod 700 log \
17+
&& touch /app/services/admin-backend/log/logfile.log && chmod 600 /app/services/admin-backend/log/logfile.log
2218

23-
RUN poetry config virtualenvs.create false && \
24-
cd /app/services/admin-backend && \
25-
if [ "$dev" = "1" ]; then \
26-
poetry install --no-interaction --no-ansi --no-root --with dev && \
27-
cd /app/libs/rag-core-lib && poetry install --no-interaction --no-ansi --no-root --with dev && \
28-
cd /app/libs/admin-api-lib && poetry install --no-interaction --no-ansi --no-root --with dev; \
29-
else \
30-
poetry install --no-interaction --no-ansi --no-root && \
31-
cd /app/libs/rag-core-lib && poetry install --no-interaction --no-ansi --no-root && \
32-
cd /app/libs/admin-api-lib && poetry install --no-interaction --no-ansi --no-root; \
33-
fi
19+
RUN poetry config virtualenvs.create false \
20+
&& cd /app/services/admin-backend \
21+
&& poetry install --no-interaction --no-ansi --no-root --with prod
3422

35-
FROM --platform=linux/amd64 python:3.13-bookworm
36-
ARG dev=0
23+
FROM python:3.13-bookworm
3724

3825
WORKDIR /app/services/admin-backend
3926

@@ -46,23 +33,14 @@ COPY --from=build /usr/bin/make /usr/bin/make
4633
COPY --from=build /usr/local/lib/ /usr/local/lib/
4734

4835
# cleanup
49-
RUN apt-get clean autoclean
50-
RUN apt-get autoremove --yes
51-
52-
RUN if [ "$dev" = "0" ]; then \
53-
while read -r shell; do rm -f "$shell"; done < /etc/shells; \
54-
rm -rf /var/lib/{apt,dpkg,cache,log}/; \
55-
fi
56-
36+
RUN apt-get clean autoclean && apt-get autoremove --yes \
37+
&& while read -r shell; do rm -f "$shell"; done < /etc/shells || true \
38+
&& rm -rf /var/lib/{apt,dpkg,cache,log}/ || true
5739

5840
ENV VIRTUAL_ENV="${POETRY_VIRTUALENVS_PATH}"
5941
ENV PATH="${POETRY_VIRTUALENVS_PATH}/bin:${PATH}"
60-
# Ensure libs are importable at runtime without installing them as packages
61-
ENV PYTHONPATH="/app/services/admin-backend/src:/app/libs/admin-api-lib/src:/app/libs/rag-core-lib/src"
6242

6343
USER nonroot
6444

6545
COPY --chown=nonroot:nonroot services/admin-backend .
6646
COPY --from=build --chown=nonroot:nonroot /app/services/admin-backend/log /app/services/admin-backend/log
67-
COPY --chown=nonroot:nonroot libs/admin-api-lib /app/libs/admin-api-lib
68-
COPY --chown=nonroot:nonroot libs/rag-core-lib /app/libs/rag-core-lib
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
FROM python:3.13-bookworm
2+
3+
# Dev image: source-based, fast iteration
4+
ENV POETRY_VIRTUALENVS_PATH=/app/services/admin-backend/.venv
5+
ENV VIRTUAL_ENV="${POETRY_VIRTUALENVS_PATH}"
6+
ENV POETRY_VERSION=2.1.3
7+
8+
WORKDIR /app
9+
10+
RUN DEBIAN_FRONTEND=noninteractive apt-get update \
11+
&& DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
12+
build-essential make \
13+
&& python3 -m venv "${POETRY_VIRTUALENVS_PATH}" \
14+
&& ${POETRY_VIRTUALENVS_PATH}/bin/pip install "poetry==${POETRY_VERSION}" \
15+
&& rm -rf /var/lib/apt/lists/*
16+
ENV PATH="${POETRY_VIRTUALENVS_PATH}/bin:$PATH"
17+
18+
# Copy lockfiles first for caching
19+
COPY services/admin-backend/pyproject.toml services/admin-backend/poetry.lock /app/services/admin-backend/
20+
COPY libs/rag-core-lib/pyproject.toml libs/rag-core-lib/poetry.lock /app/libs/rag-core-lib/
21+
COPY libs/admin-api-lib/pyproject.toml libs/admin-api-lib/poetry.lock /app/libs/admin-api-lib/
22+
23+
# Install deps (with dev extras) without packaging
24+
RUN poetry config virtualenvs.create false \
25+
&& cd /app/services/admin-backend && poetry install --no-interaction --no-ansi --no-root --with dev --without prod \
26+
&& cd /app/libs/rag-core-lib && poetry install --no-interaction --no-ansi --no-root --with dev \
27+
&& cd /app/libs/admin-api-lib && poetry install --no-interaction --no-ansi --no-root --with dev
28+
29+
# Create non-root user
30+
RUN adduser --disabled-password --gecos "" --uid 65532 nonroot
31+
32+
WORKDIR /app/services/admin-backend
33+
RUN mkdir -p log && chmod 700 log \
34+
&& touch /app/services/admin-backend/log/logfile.log && chmod 600 /app/services/admin-backend/log/logfile.log
35+
36+
# Ensure importability via PYTHONPATH
37+
ENV PYTHONPATH="/app/services/admin-backend/src:/app/libs/admin-api-lib/src:/app/libs/rag-core-lib/src"
38+
39+
# Copy sources last
40+
COPY --chown=nonroot:nonroot services/admin-backend /app/services/admin-backend
41+
COPY --chown=nonroot:nonroot libs/rag-core-lib /app/libs/rag-core-lib
42+
COPY --chown=nonroot:nonroot libs/admin-api-lib /app/libs/admin-api-lib
43+
44+
USER nonroot
45+
46+
# Entrypoint handled externally

services/admin-backend/pyproject.toml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,3 +90,13 @@ build-backend = "poetry.core.masonry.api"
9090

9191
[tool.poetry.dependencies]
9292
python = "^3.13"
93+
94+
# Prefer PyPI, but allow resolving from TestPyPI for internal libs
95+
[[tool.poetry.source]]
96+
name = "testpypi"
97+
url = "https://test.pypi.org/simple/"
98+
priority = "supplemental"
99+
100+
[tool.poetry.group.prod.dependencies]
101+
admin-api-lib = "==2.2.0"
102+
rag-core-lib = "==2.2.0"

0 commit comments

Comments
 (0)