Skip to content

Commit 7cae3ca

Browse files
chrisburrchaen
andauthored
feat: replace container base images with pixi-managed environments (#810)
* chore: importlib-metadata should be constrained on diracx-routers * fix: Force importlib-metadata = "<8.8.0" due to https://pixi.sh/latest/concepts/conda_pypi/#pinned-package-conflicts * fix: enable OTel log record injection to avoid KeyError with sdk >=1.28.0 Since opentelemetry-sdk v1.28.0 (open-telemetry/opentelemetry-python#4166), LoggingHandler._translate() calls self.format(record) when a formatter is set. The existing formatter uses %(otelTraceID)s placeholders but set_logging_format=False meant the LoggingInstrumentor never injected those attributes, causing a KeyError at runtime. * chore: replace container base images with pixi-managed environments * ci: Simplify running legacy DIRAC integration tests * test: correct verify_entry_points to handle extensions and duplicate names * ci: improve debugging output of legacy integration tests --------- Co-authored-by: Christophe Haen <christophe.haen@cern.ch>
1 parent e552651 commit 7cae3ca

24 files changed

Lines changed: 22630 additions & 208 deletions

File tree

.dockerignore

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Pixi environments (can be very large)
2+
.pixi/
3+
4+
# Documentation build output
5+
site/
6+
7+
# Python build artifacts
8+
*.egg-info/
9+
__pycache__/

.github/workflows/deployment.yml

Lines changed: 23 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ jobs:
9797

9898
docker:
9999
needs: deploy-pypi
100-
timeout-minutes: 30
100+
timeout-minutes: 30
101101
runs-on: ubuntu-latest
102102
steps:
103103
- name: Checkout
@@ -112,58 +112,46 @@ jobs:
112112
registry: ghcr.io
113113
username: ${{ github.actor }}
114114
password: ${{ secrets.GITHUB_TOKEN }}
115-
- name: Download diracx wheels
116-
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
117-
with:
118-
name: diracx-whl
119-
- name: "Find wheels"
120-
id: find_wheel
121-
run: |
122-
# We need to copy them there to be able to access them in the RUN --mount
123-
cp diracx*.whl containers/client/
124-
cp diracx*.whl containers/services/
125-
for wheel_fn in *.whl; do
126-
pkg_name=$(basename "${wheel_fn}" | cut -d '-' -f 1)
127-
echo "${pkg_name}-wheel-name=$(ls "${pkg_name}"-*.whl)" >> $GITHUB_OUTPUT
128-
done
129115

130-
- name: Build and push client (release)
116+
- name: Build and push services (release)
131117
uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7
132118
if: ${{ needs.deploy-pypi.outputs.create-release == 'true' }}
133119
with:
134-
context: containers/client/
135-
push: ${{ needs.deploy-pypi.outputs.create-release == 'true' }}
136-
tags: "ghcr.io/diracgrid/diracx/client:${{ needs.deploy-pypi.outputs.new-version }}"
120+
context: .
121+
file: containers/Dockerfile
122+
build-args: PIXI_ENV=container-services
123+
push: true
124+
tags: "ghcr.io/diracgrid/diracx/services:${{ needs.deploy-pypi.outputs.new-version }}"
137125
platforms: linux/amd64,linux/arm64
138-
build-args: EXTRA_PACKAGES_TO_INSTALL=DIRACCommon~=9.0.0
139-
- name: Build and push services (release)
126+
- name: Build and push client (release)
140127
uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7
141128
if: ${{ needs.deploy-pypi.outputs.create-release == 'true' }}
142129
with:
143-
context: containers/services/
144-
push: ${{ needs.deploy-pypi.outputs.create-release == 'true' }}
145-
tags: "ghcr.io/diracgrid/diracx/services:${{ needs.deploy-pypi.outputs.new-version }}"
130+
context: .
131+
file: containers/Dockerfile
132+
build-args: PIXI_ENV=container-client
133+
push: true
134+
tags: "ghcr.io/diracgrid/diracx/client:${{ needs.deploy-pypi.outputs.new-version }}"
146135
platforms: linux/amd64,linux/arm64
147-
build-args: EXTRA_PACKAGES_TO_INSTALL=DIRACCommon~=9.0.0
148136

149-
- name: Build and push client (dev)
137+
- name: Build and push services (dev)
150138
uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7
151139
with:
152-
context: containers/client/
140+
context: .
141+
file: containers/Dockerfile
142+
build-args: PIXI_ENV=container-services
153143
push: ${{ github.event_name != 'pull_request' && github.repository == 'DIRACGrid/diracx' && github.ref_name == 'main' }}
154-
tags: ghcr.io/diracgrid/diracx/client:dev
144+
tags: ghcr.io/diracgrid/diracx/services:dev
155145
platforms: linux/amd64,linux/arm64
156-
build-args: |
157-
EXTRA_PACKAGES_TO_INSTALL=git+https://github.com/DIRACGrid/DIRAC.git@integration#egg=diraccommon\&subdirectory=dirac-common
158-
- name: Build and push services (dev)
146+
- name: Build and push client (dev)
159147
uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7
160148
with:
161-
context: containers/services/
149+
context: .
150+
file: containers/Dockerfile
151+
build-args: PIXI_ENV=container-client
162152
push: ${{ github.event_name != 'pull_request' && github.repository == 'DIRACGrid/diracx' && github.ref_name == 'main' }}
163-
tags: ghcr.io/diracgrid/diracx/services:dev
153+
tags: ghcr.io/diracgrid/diracx/client:dev
164154
platforms: linux/amd64,linux/arm64
165-
build-args: |
166-
EXTRA_PACKAGES_TO_INSTALL=git+https://github.com/DIRACGrid/DIRAC.git@integration#egg=diraccommon\&subdirectory=dirac-common
167155

168156
update-charts:
169157
name: Update Helm charts

.github/workflows/integration.yml

Lines changed: 26 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -29,21 +29,13 @@ jobs:
2929
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6
3030
with:
3131
python-version: '3.14'
32-
- name: Build wheels
33-
run: |
34-
pip install build
35-
for pkg_dir in $PWD/diracx-* .; do
36-
echo "Building $pkg_dir"
37-
python -m build --outdir "${GITHUB_WORKSPACE}/dist" $pkg_dir
38-
done
3932
- name: Clone DIRAC
4033
run: |
4134
pip install typer pyyaml gitpython packaging
4235
git clone https://github.com/DIRACGrid/DIRAC.git -b "${{ matrix.dirac-branch }}" /tmp/DIRACRepo
4336
echo "Current revision: $(git -C /tmp/DIRACRepo rev-parse HEAD)"
44-
# We need to cd in the directory for the integration_tests.py to work
4537
- name: Prepare environment
46-
run: cd /tmp/DIRACRepo && ./integration_tests.py prepare-environment "TEST_DIRACX=Yes" --diracx-dist-dir "${GITHUB_WORKSPACE}/dist"
38+
run: cd /tmp/DIRACRepo && ./integration_tests.py prepare-environment "TEST_DIRACX=Yes" --diracx-src-dir "${GITHUB_WORKSPACE}"
4739
- name: Install server
4840
run: cd /tmp/DIRACRepo && ./integration_tests.py install-server
4941
- name: Install client
@@ -64,19 +56,35 @@ jobs:
6456
if [ -f client-tests-failed ]; then has_error=1; echo "Client tests failed"; fi
6557
if [ -f pilot-tests-failed ]; then has_error=1; echo "Pilot tests failed"; fi
6658
if [ ${has_error} = 1 ]; then exit 1; fi
67-
- name: diracx filtered requests logs
59+
- name: Debugging information
60+
if: always()
6861
run: |
62+
mkdir -p /tmp/service-logs
63+
64+
echo "::group::Container status"
65+
docker ps -a --format "table {{.Names}}\t{{.Status}}\t{{.Image}}\t{{.Ports}}"
66+
echo "::endgroup::"
67+
68+
DIRACX_CONTAINERS="diracx diracx-init-keystore diracx-init-cs diracx-init-db diracx-chmod"
69+
70+
for container in $DIRACX_CONTAINERS; do
71+
if docker inspect "$container" &>/dev/null; then
72+
echo "::group::Logs: $container"
73+
docker logs "$container" 2>&1 | tee -a /tmp/service-logs/diracx.log
74+
echo "::endgroup::"
75+
fi
76+
done
77+
78+
echo "::group::DiracX API request summary"
6979
docker logs diracx 2>/dev/null | grep '/api/' \
7080
| awk -F\" '{print $2, $3}' \
7181
| awk '{print $1, $2, $4}' \
72-
| sort | uniq -c | sort
73-
- name: diracx error logs
74-
if: ${{ failure() }}
75-
run: |
76-
mkdir -p /tmp/service-logs
77-
docker logs diracx 2>&1 | tee /tmp/service-logs/diracx.log
78-
cd /tmp/DIRACRepo
79-
./integration_tests.py logs --no-follow --lines 1000 2>&1 | tee /tmp/service-logs/dirac.log
82+
| sort | uniq -c | sort || true
83+
echo "::endgroup::"
84+
85+
echo "::group::DIRAC server logs"
86+
cd /tmp/DIRACRepo && ./integration_tests.py logs --no-follow --lines 1000 2>&1 | tee /tmp/service-logs/dirac.log || true
87+
echo "::endgroup::"
8088
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
8189
if: ${{ failure() }}
8290
with:

.github/workflows/main.yml

Lines changed: 31 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -121,44 +121,32 @@ jobs:
121121
with:
122122
cache: false
123123
environments: ${{ matrix.extension == 'diracx' && 'default' || 'default-gubbins' }}
124-
- name: Build gubbins wheels
125-
if: ${{ matrix.extension == 'gubbins' }}
126-
run: |
127-
for pkg_dir in $PWD/diracx-*; do
128-
echo "Building $pkg_dir"
129-
pixi exec python-build --outdir $PWD/extensions/containers/services/ $pkg_dir
130-
done
131-
# Also build the diracx metapackage
132-
pixi exec python-build --outdir $PWD/extensions/containers/services/ .
133-
# And build the gubbins package
134-
for pkg_dir in $PWD/extensions/gubbins/gubbins-*; do
135-
# Skip the testing package
136-
if [[ "${pkg_dir}" =~ .*testing.* ]];
137-
then
138-
echo "Do not build ${pkg_dir}";
139-
continue;
140-
fi
141-
echo "Building $pkg_dir"
142-
pixi exec python-build --outdir $PWD/extensions/containers/services/ $pkg_dir
143-
done
144124
- name: Set up Docker Buildx
145-
if: ${{ matrix.extension == 'gubbins' }}
146125
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4
147-
- name: Build container for gubbins
148-
if: ${{ matrix.extension == 'gubbins' }}
126+
- name: Build services image
127+
uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7
128+
with:
129+
context: .
130+
file: containers/Dockerfile
131+
build-args: |
132+
PIXI_ENV=${{ matrix.extension == 'diracx' && 'container-services' || 'gubbins-container-services' }}
133+
tags: ghcr.io/${{ matrix.extension == 'diracx' && 'diracgrid/diracx' || 'gubbins' }}/services:dev
134+
outputs: type=docker,dest=/tmp/services_image.tar
135+
- name: Build client image
149136
uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7
150137
with:
151-
context: extensions/containers/services
152-
tags: gubbins/services:dev
153-
outputs: type=docker,dest=/tmp/gubbins_services_image.tar
138+
context: .
139+
file: containers/Dockerfile
154140
build-args: |
155-
EXTENSION_CUSTOM_SOURCES_TO_INSTALL=/bindmount/gubbins_db*.whl,/bindmount/gubbins_logic*.whl,/bindmount/gubbins_routers*.whl,/bindmount/gubbins_client*.whl
156-
- name: Load image
157-
if: ${{ matrix.extension == 'gubbins' }}
141+
PIXI_ENV=${{ matrix.extension == 'diracx' && 'container-client' || 'gubbins-container-client' }}
142+
tags: ghcr.io/${{ matrix.extension == 'diracx' && 'diracgrid/diracx' || 'gubbins' }}/client:dev
143+
outputs: type=docker,dest=/tmp/client_image.tar
144+
- name: Load images
158145
run: |
159-
docker load --input /tmp/gubbins_services_image.tar
160-
# Free up disk space by removing the tarball after loading the image
161-
rm -Rf /tmp/gubbins_services_image.tar
146+
docker load --input /tmp/services_image.tar
147+
rm -f /tmp/services_image.tar
148+
docker load --input /tmp/client_image.tar
149+
rm -f /tmp/client_image.tar
162150
docker builder prune -af || true
163151
docker image ls -a
164152
- name: Start demo
@@ -191,16 +179,26 @@ jobs:
191179
# Copy gubbins-charts to a temporary location and build dependencies
192180
cp -r ./extensions/gubbins-charts /tmp/
193181
../diracx-charts/.demo/helm dependency build /tmp/gubbins-charts
182+
# Replace the downloaded subchart with the locally-cloned one to
183+
# ensure we have the pixi-compatible ConfigMap entrypoint
184+
rm -f /tmp/gubbins-charts/charts/diracx-*.tgz
185+
../diracx-charts/.demo/helm package ../diracx-charts/diracx -d /tmp/gubbins-charts/charts/
194186
195187
demo_args+=("--extension-chart-path" "/tmp/gubbins-charts")
196188
demo_args+=("--ci-values" "./extensions/gubbins_values.yaml")
197-
demo_args+=("--load-docker-image" "gubbins/services:dev")
189+
demo_args+=("--load-docker-image" "ghcr.io/gubbins/services:dev")
190+
demo_args+=("--load-docker-image" "ghcr.io/gubbins/client:dev")
191+
demo_args+=("--prune-loaded-images")
198192
demo_source_dirs+=("/tmp/gubbins/")
199193
elif [ ${{ matrix.extension }} != 'diracx' ]; then
200194
echo "Unknown extension: ${{ matrix.extension }}"
201195
exit 1
202196
else
203197
demo_args+=("--set-value" "developer.autoReload=false")
198+
demo_args+=("--set-value" "global.imagePullPolicy=IfNotPresent")
199+
demo_args+=("--load-docker-image" "ghcr.io/diracgrid/diracx/services:dev")
200+
demo_args+=("--load-docker-image" "ghcr.io/diracgrid/diracx/client:dev")
201+
demo_args+=("--prune-loaded-images")
204202
fi
205203
206204
# Run the demo with the provided arguments

.gitignore

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,6 @@ docs/source/_build
9797

9898
# pixi environments
9999
.pixi
100-
pixi.lock
101100
*.egg-info
102101
docs/templates/_builtin_markdown.jinja
103102

@@ -106,3 +105,7 @@ docs/templates/_builtin_markdown.jinja
106105

107106
# docs site
108107
site
108+
109+
# DiracX specific
110+
# No point in committing the pixi.lock for gubbins as it cannot work properly within the diracx repo
111+
extensions/gubbins/pixi.lock

.pre-commit-config.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ repos:
2828
- id: check-yaml
2929
args: ["--unsafe"]
3030
- id: check-added-large-files
31+
exclude: pixi\.lock$
3132

3233
- repo: https://github.com/astral-sh/ruff-pre-commit
3334
rev: "v0.15.7"
@@ -66,6 +67,7 @@ repos:
6667
hooks:
6768
- id: codespell
6869
args: ["-w"]
70+
exclude: pixi\.lock$
6971

7072
- repo: https://github.com/adamchainz/blacken-docs
7173
rev: 1.20.0

containers/Dockerfile

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
FROM ghcr.io/prefix-dev/pixi:latest AS build
2+
3+
ARG PIXI_ENV=container-services
4+
5+
WORKDIR /app
6+
COPY pixi.toml pixi.lock ./
7+
8+
# Copy source directories needed for path-based installs
9+
COPY .git/ .git/
10+
COPY diracx-api/ diracx-api/
11+
COPY diracx-cli/ diracx-cli/
12+
COPY diracx-client/ diracx-client/
13+
COPY diracx-core/ diracx-core/
14+
COPY diracx-db/ diracx-db/
15+
COPY diracx-logic/ diracx-logic/
16+
COPY diracx-routers/ diracx-routers/
17+
COPY diracx-testing/ diracx-testing/
18+
COPY pyproject.toml ./
19+
COPY README.md ./
20+
COPY extensions/gubbins/gubbins-api/ extensions/gubbins/gubbins-api/
21+
COPY extensions/gubbins/gubbins-cli/ extensions/gubbins/gubbins-cli/
22+
COPY extensions/gubbins/gubbins-client/ extensions/gubbins/gubbins-client/
23+
COPY extensions/gubbins/gubbins-core/ extensions/gubbins/gubbins-core/
24+
COPY extensions/gubbins/gubbins-db/ extensions/gubbins/gubbins-db/
25+
COPY extensions/gubbins/gubbins-logic/ extensions/gubbins/gubbins-logic/
26+
COPY extensions/gubbins/gubbins-routers/ extensions/gubbins/gubbins-routers/
27+
COPY extensions/gubbins/gubbins-testing/ extensions/gubbins/gubbins-testing/
28+
COPY extensions/gubbins/pyproject.toml extensions/gubbins/
29+
COPY extensions/gubbins/README.md extensions/gubbins/
30+
31+
# Switch to non-editable installs (same workaround as CI)
32+
RUN sed -i 's@editable = true@editable = false@g' pixi.toml
33+
34+
RUN pixi install --frozen -e ${PIXI_ENV}
35+
36+
# Generate activation script
37+
RUN pixi shell-hook -e ${PIXI_ENV} > /activate.sh
38+
39+
FROM ubuntu:24.04
40+
41+
ARG PIXI_ENV=container-services
42+
ENV PIXI_ENV=${PIXI_ENV}
43+
44+
EXPOSE 8000
45+
46+
# Copy the installed environment
47+
COPY --from=build /app/.pixi/envs/${PIXI_ENV} /app/.pixi/envs/${PIXI_ENV}
48+
COPY --from=build /activate.sh /activate.sh
49+
COPY containers/entrypoint.sh /entrypoint.sh
50+
51+
# In many clusters the container is run as a random uid for security reasons.
52+
# If we mark the environment directory as group 0 and give it group write
53+
# permissions then we're still able to manage the environment from inside the
54+
# container.
55+
RUN chmod -R g=u /app/.pixi
56+
57+
# Compatibility shim: the Helm chart invokes this micromamba entrypoint path
58+
RUN printf '#!/bin/bash\nsource /activate.sh\nexec "$@"\n' > /usr/local/bin/_entrypoint.sh && \
59+
chmod +x /usr/local/bin/_entrypoint.sh
60+
61+
# Use tini as init (installed by pixi in the env)
62+
ENTRYPOINT ["/bin/bash", "-c", \
63+
"exec /app/.pixi/envs/${PIXI_ENV}/bin/tini -- /entrypoint.sh \"$@\"", "--"]

containers/client/Dockerfile

Lines changed: 0 additions & 12 deletions
This file was deleted.

containers/entrypoint.sh

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#!/bin/bash
2+
set -e
3+
4+
source /activate.sh
5+
6+
exec "$@"

containers/services/Dockerfile

Lines changed: 0 additions & 12 deletions
This file was deleted.

0 commit comments

Comments
 (0)