Skip to content

Commit b08d0a7

Browse files
author
Andrei Neagu
committed
added new service
1 parent 5033ace commit b08d0a7

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+1659
-0
lines changed

services/notifications/Dockerfile

Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
# syntax=docker/dockerfile:1
2+
3+
# Define arguments in the global scope
4+
ARG PYTHON_VERSION="3.11.9"
5+
ARG UV_VERSION="0.5"
6+
7+
FROM ghcr.io/astral-sh/uv:${UV_VERSION} AS uv_build
8+
# we docker image is built based on debian
9+
FROM python:${PYTHON_VERSION}-slim-bookworm AS base
10+
11+
12+
#
13+
# USAGE:
14+
# cd sercices/notifications
15+
# docker build -f Dockerfile -t notifications:prod --target production ../../
16+
# docker run notifications:prod
17+
#
18+
# REQUIRED: context expected at ``osparc-simcore/`` folder because we need access to osparc-simcore/packages
19+
20+
LABEL maintainer=GitHK
21+
22+
# for docker apt caching to work this needs to be added: [https://vsupalov.com/buildkit-cache-mount-dockerfile/]
23+
RUN rm -f /etc/apt/apt.conf.d/docker-clean && \
24+
echo 'Binary::apt::APT::Keep-Downloaded-Packages "true";' > /etc/apt/apt.conf.d/keep-cache
25+
RUN --mount=type=cache,target=/var/cache/apt,sharing=private \
26+
set -eux && \
27+
apt-get update && \
28+
apt-get install -y --no-install-recommends \
29+
gosu \
30+
curl \
31+
&& apt-get clean -y \
32+
# verify that the binary works
33+
&& gosu nobody true
34+
35+
# simcore-user uid=8004(scu) gid=8004(scu) groups=8004(scu)
36+
ENV SC_USER_ID=8004 \
37+
SC_USER_NAME=scu \
38+
SC_BUILD_TARGET=base \
39+
SC_BOOT_MODE=default
40+
41+
RUN adduser \
42+
--uid ${SC_USER_ID} \
43+
--disabled-password \
44+
--gecos "" \
45+
--shell /bin/sh \
46+
--home /home/${SC_USER_NAME} \
47+
${SC_USER_NAME}
48+
49+
50+
# Sets utf-8 encoding for Python et al
51+
ENV LANG=C.UTF-8
52+
53+
# Turns off writing .pyc files; superfluous on an ephemeral container.
54+
ENV PYTHONDONTWRITEBYTECODE=1 \
55+
VIRTUAL_ENV=/home/scu/.venv
56+
57+
# Ensures that the python and pip executables used in the image will be
58+
# those from our virtualenv.
59+
ENV PATH="${VIRTUAL_ENV}/bin:$PATH"
60+
61+
# rclone installation
62+
ARG TARGETARCH
63+
ENV TARGETARCH=${TARGETARCH}
64+
RUN \
65+
--mount=type=bind,source=scripts/install_rclone.bash,target=/tmp/install_rclone.bash \
66+
./tmp/install_rclone.bash
67+
68+
# -------------------------- Build stage -------------------
69+
# Installs build/package management tools and third party dependencies
70+
#
71+
# + /build WORKDIR
72+
#
73+
FROM base AS build
74+
75+
ENV SC_BUILD_TARGET=build
76+
77+
RUN --mount=type=cache,target=/var/cache/apt,sharing=private \
78+
set -eux \
79+
&& apt-get update \
80+
&& apt-get install -y --no-install-recommends \
81+
build-essential
82+
83+
# install UV https://docs.astral.sh/uv/guides/integration/docker/#installing-uv
84+
COPY --from=uv_build /uv /uvx /bin/
85+
86+
# NOTE: python virtualenv is used here such that installed
87+
# packages may be moved to production image easily by copying the venv
88+
RUN uv venv "${VIRTUAL_ENV}"
89+
90+
RUN --mount=type=cache,target=/root/.cache/uv \
91+
uv pip install --upgrade \
92+
wheel \
93+
setuptools
94+
95+
WORKDIR /build
96+
97+
# install base 3rd party dependencies
98+
99+
100+
101+
# --------------------------Prod-depends-only stage -------------------
102+
# This stage is for production only dependencies that get partially wiped out afterwards (final docker image concerns)
103+
#
104+
# + /build
105+
# + services/notifications [scu:scu] WORKDIR
106+
#
107+
FROM build AS prod-only-deps
108+
109+
ENV SC_BUILD_TARGET prod-only-deps
110+
111+
WORKDIR /build/services/notifications
112+
113+
RUN \
114+
--mount=type=bind,source=packages,target=/build/packages,rw \
115+
--mount=type=bind,source=services/notifications,target=/build/services/notifications,rw \
116+
--mount=type=cache,target=/root/.cache/uv \
117+
uv pip sync \
118+
requirements/prod.txt \
119+
&& uv pip list
120+
121+
122+
# --------------------------Production stage -------------------
123+
# Final cleanup up to reduce image size and startup setup
124+
# Runs as scu (non-root user)
125+
#
126+
# + /home/scu $HOME = WORKDIR
127+
# + services/notifications [scu:scu]
128+
#
129+
FROM base AS production
130+
131+
ENV SC_BUILD_TARGET=production \
132+
SC_BOOT_MODE=production
133+
134+
ENV PYTHONOPTIMIZE=TRUE
135+
# https://docs.astral.sh/uv/guides/integration/docker/#compiling-bytecode
136+
ENV UV_COMPILE_BYTECODE=1
137+
138+
WORKDIR /home/scu
139+
140+
# ensure home folder is read/writable for user scu
141+
RUN chown -R scu /home/scu
142+
143+
# Starting from clean base image, copies pre-installed virtualenv from prod-only-deps
144+
COPY --chown=scu:scu --from=prod-only-deps ${VIRTUAL_ENV} ${VIRTUAL_ENV}
145+
146+
# Copies booting scripts
147+
COPY --chown=scu:scu services/notifications/docker services/notifications/docker
148+
RUN chmod +x services/notifications/docker/*.sh
149+
150+
151+
HEALTHCHECK --interval=30s \
152+
--timeout=20s \
153+
--start-period=30s \
154+
--retries=3 \
155+
CMD ["python3", "services/notifications/docker/healthcheck.py", "http://localhost:8000/health"]
156+
157+
EXPOSE 8000
158+
159+
ENTRYPOINT [ "/bin/sh", "services/notifications/docker/entrypoint.sh" ]
160+
CMD ["/bin/sh", "services/notifications/docker/boot.sh"]
161+
162+
163+
# --------------------------Development stage -------------------
164+
# Source code accessible in host but runs in container
165+
# Runs as myu with same gid/uid as host
166+
# Placed at the end to speed-up the build if images targeting production
167+
#
168+
# + /devel WORKDIR
169+
# + services (mounted volume)
170+
#
171+
FROM build AS development
172+
173+
ENV SC_BUILD_TARGET=development \
174+
SC_DEVEL_MOUNT=/devel/services/notifications
175+
176+
WORKDIR /devel
177+
178+
RUN chown -R scu:scu "${VIRTUAL_ENV}"
179+
180+
EXPOSE 8000
181+
EXPOSE 3000
182+
183+
ENTRYPOINT ["/bin/sh", "services/notifications/docker/entrypoint.sh"]
184+
CMD ["/bin/sh", "services/notifications/docker/boot.sh"]

services/notifications/Makefile

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#
2+
# DEVELOPMENT recipes for notifications
3+
#
4+
include ../../scripts/common.Makefile
5+
include ../../scripts/common-service.Makefile

services/notifications/README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# notifications
2+
3+
4+
To develop this project, just
5+
6+
```cmd
7+
make help
8+
9+
```

services/notifications/VERSION

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
0.0.1
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
#!/bin/sh
2+
set -o errexit
3+
set -o nounset
4+
5+
IFS=$(printf '\n\t')
6+
7+
INFO="INFO: [$(basename "$0")] "
8+
9+
echo "$INFO" "Booting in ${SC_BOOT_MODE} mode ..."
10+
echo "$INFO" "User :$(id "$(whoami)")"
11+
echo "$INFO" "Workdir : $(pwd)"
12+
13+
#
14+
# DEVELOPMENT MODE
15+
#
16+
# - prints environ info
17+
# - installs requirements in mounted volume
18+
#
19+
if [ "${SC_BUILD_TARGET}" = "development" ]; then
20+
echo "$INFO" "Environment :"
21+
printenv | sed 's/=/: /' | sed 's/^/ /' | sort
22+
echo "$INFO" "Python :"
23+
python --version | sed 's/^/ /'
24+
command -v python | sed 's/^/ /'
25+
26+
cd services/notifications
27+
uv pip --quiet sync requirements/dev.txt
28+
cd -
29+
echo "$INFO" "PIP :"
30+
uv pip list
31+
fi
32+
33+
if [ "${SC_BOOT_MODE}" = "debug" ]; then
34+
# NOTE: production does NOT pre-installs debugpy
35+
uv pip install debugpy
36+
fi
37+
38+
#
39+
# RUNNING application
40+
#
41+
APP_LOG_LEVEL=${LOGLEVEL:-${LOG_LEVEL:-${LOGLEVEL:-INFO}}}
42+
NOTIFICATIONS_SERVER_REMOTE_DEBUG_PORT=3000
43+
SERVER_LOG_LEVEL=$(echo "${APP_LOG_LEVEL}" | tr '[:upper:]' '[:lower:]')
44+
echo "$INFO" "Log-level app/server: $APP_LOG_LEVEL/$SERVER_LOG_LEVEL"
45+
46+
if [ "${SC_BOOT_MODE}" = "debug" ]; then
47+
reload_dir_packages=$(find /devel/packages -maxdepth 3 -type d -path "*/src/*" ! -path "*.*" -exec echo '--reload-dir {} \' \;)
48+
49+
exec sh -c "
50+
cd services/notifications/src/simcore_service_notifications && \
51+
python -m debugpy --listen 0.0.0.0:${NOTIFICATIONS_SERVER_REMOTE_DEBUG_PORT} -m uvicorn main:the_app \
52+
--host 0.0.0.0 \
53+
--port 8000 \
54+
--reload \
55+
$reload_dir_packages
56+
--reload-dir . \
57+
--log-level \"${SERVER_LOG_LEVEL}\"
58+
"
59+
else
60+
exec uvicorn simcore_service_notifications.main:the_app \
61+
--host 0.0.0.0 \
62+
--port 8000 \
63+
--log-level "${SERVER_LOG_LEVEL}" \
64+
--no-access-log
65+
fi
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
#!/bin/sh
2+
#
3+
# - Executes *inside* of the container upon start as --user [default root]
4+
# - Notice that the container *starts* as --user [default root] but
5+
# *runs* as non-root user [scu]
6+
#
7+
set -o errexit
8+
set -o nounset
9+
10+
IFS=$(printf '\n\t')
11+
12+
INFO="INFO: [$(basename "$0")] "
13+
WARNING="WARNING: [$(basename "$0")] "
14+
ERROR="ERROR: [$(basename "$0")] "
15+
16+
echo "$INFO" "Entrypoint for stage ${SC_BUILD_TARGET} ..."
17+
echo "$INFO" "User :$(id "$(whoami)")"
18+
echo "$INFO" "Workdir : $(pwd)"
19+
echo "$INFO" "User : $(id scu)"
20+
echo "$INFO" "python : $(command -v python)"
21+
echo "$INFO" "pip : $(command -v pip)"
22+
23+
#
24+
# DEVELOPMENT MODE
25+
# - expects docker run ... -v $(pwd):$SC_DEVEL_MOUNT
26+
# - mounts source folders
27+
# - deduces host's uid/gip and assigns to user within docker
28+
#
29+
if [ "${SC_BUILD_TARGET}" = "development" ]; then
30+
echo "$INFO" "development mode detected..."
31+
stat "${SC_DEVEL_MOUNT}" >/dev/null 2>&1 ||
32+
(echo "$ERROR" "You must mount '$SC_DEVEL_MOUNT' to deduce user and group ids" && exit 1)
33+
34+
echo "$INFO" "setting correct user id/group id..."
35+
HOST_USERID=$(stat --format=%u "${SC_DEVEL_MOUNT}")
36+
HOST_GROUPID=$(stat --format=%g "${SC_DEVEL_MOUNT}")
37+
CONT_GROUPNAME=$(getent group "${HOST_GROUPID}" | cut --delimiter=: --fields=1)
38+
if [ "$HOST_USERID" -eq 0 ]; then
39+
echo "$WARNING" "Folder mounted owned by root user... adding $SC_USER_NAME to root..."
40+
adduser "$SC_USER_NAME" root
41+
else
42+
echo "$INFO" "Folder mounted owned by user $HOST_USERID:$HOST_GROUPID-'$CONT_GROUPNAME'..."
43+
# take host's credentials in $SC_USER_NAME
44+
if [ -z "$CONT_GROUPNAME" ]; then
45+
echo "$WARNING" "Creating new group grp$SC_USER_NAME"
46+
CONT_GROUPNAME=grp$SC_USER_NAME
47+
addgroup --gid "$HOST_GROUPID" "$CONT_GROUPNAME"
48+
else
49+
echo "$INFO" "group already exists"
50+
fi
51+
echo "$INFO" "Adding $SC_USER_NAME to group $CONT_GROUPNAME..."
52+
adduser "$SC_USER_NAME" "$CONT_GROUPNAME"
53+
54+
echo "$WARNING" "Changing ownership [this could take some time]"
55+
echo "$INFO" "Changing $SC_USER_NAME:$SC_USER_NAME ($SC_USER_ID:$SC_USER_ID) to $SC_USER_NAME:$CONT_GROUPNAME ($HOST_USERID:$HOST_GROUPID)"
56+
usermod --uid "$HOST_USERID" --gid "$HOST_GROUPID" "$SC_USER_NAME"
57+
58+
echo "$INFO" "Changing group properties of files around from $SC_USER_ID to group $CONT_GROUPNAME"
59+
find / -path /proc -prune -o -group "$SC_USER_ID" -exec chgrp --no-dereference "$CONT_GROUPNAME" {} \;
60+
# change user property of files already around
61+
echo "$INFO" "Changing ownership properties of files around from $SC_USER_ID to group $CONT_GROUPNAME"
62+
find / -path /proc -prune -o -user "$SC_USER_ID" -exec chown --no-dereference "$SC_USER_NAME" {} \;
63+
fi
64+
fi
65+
66+
67+
echo "$INFO Starting $* ..."
68+
echo " $SC_USER_NAME rights : $(id "$SC_USER_NAME")"
69+
echo " local dir : $(ls -al)"
70+
71+
exec gosu "$SC_USER_NAME" "$@"
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
#!/bin/python
2+
""" Healthcheck script to run inside docker
3+
4+
Example of usage in a Dockerfile
5+
```
6+
COPY --chown=scu:scu docker/healthcheck.py docker/healthcheck.py
7+
HEALTHCHECK --interval=30s \
8+
--timeout=30s \
9+
--start-period=1s \
10+
--retries=3 \
11+
CMD python3 docker/healthcheck.py http://localhost:8000/
12+
```
13+
14+
Q&A:
15+
1. why not to use curl instead of a python script?
16+
- SEE https://blog.sixeyed.com/docker-healthchecks-why-not-to-use-curl-or-iwr/
17+
"""
18+
import os
19+
import sys
20+
from urllib.request import urlopen
21+
22+
SUCCESS, UNHEALTHY = 0, 1
23+
24+
# Disabled if boots with debugger (e.g. debug, pdb-debug, debug-ptvsd, debugpy, etc)
25+
ok = "debug" in os.environ.get("SC_BOOT_MODE", "").lower()
26+
27+
# Queries host
28+
# pylint: disable=consider-using-with
29+
ok = (
30+
ok
31+
or urlopen(
32+
"{host}{baseurl}".format(
33+
host=sys.argv[1], baseurl=os.environ.get("SIMCORE_NODE_BASEPATH", "")
34+
) # adds a base-path if defined in environ
35+
).getcode()
36+
== 200
37+
)
38+
39+
40+
sys.exit(SUCCESS if ok else UNHEALTHY)
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#
2+
# Targets to pip-compile requirements
3+
#
4+
include ../../../requirements/base.Makefile
5+
6+
# Add here any extra explicit dependency: e.g. _migration.txt: _base.txt
7+
8+
_base.in: constraints.txt
9+
_test.in: constraints.txt
10+
_tools.in: constraints.txt

0 commit comments

Comments
 (0)