Skip to content

Commit

Permalink
Move dependencies into /data/olympia:
Browse files Browse the repository at this point in the history
/deps/ -> /data/olympia/deps
/deps/node_modules -> /data/olympia/node_modules

This effectively maps the host path for dependencies into the container directly and also means npm just "works"
  • Loading branch information
KevinMind committed Dec 31, 2024
1 parent 03e0124 commit b712f84
Show file tree
Hide file tree
Showing 15 changed files with 179 additions and 195 deletions.
48 changes: 26 additions & 22 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,24 @@ ENV BUILD_INFO=/build-info.json
SHELL ["/bin/bash", "-xue", "-c"]

ENV OLYMPIA_UID=9500
# give olympia access to the HOME directory
ENV HOME=/data/olympia
ENV DEPS_DIR=${HOME}/deps
ENV NPM_DEPS_DIR=${HOME}/node_modules

RUN <<EOF
groupadd -g ${OLYMPIA_UID} olympia
useradd -u ${OLYMPIA_UID} -g ${OLYMPIA_UID} -s /sbin/nologin -d /data/olympia olympia
useradd -u ${OLYMPIA_UID} -g ${OLYMPIA_UID} -s /sbin/nologin -d ${HOME} olympia

# Create and chown olympia directories
olympia_dirs=("${DEPS_DIR}" "${NPM_DEPS_DIR}" "${HOME}/storage")
for dir in "${olympia_dirs[@]}"; do
mkdir -p ${dir}
chown -R olympia:olympia ${dir}
done
EOF

# give olympia access to the HOME directory
ENV HOME=/data/olympia

WORKDIR ${HOME}
RUN chown -R olympia:olympia ${HOME}

Expand Down Expand Up @@ -74,37 +85,28 @@ ENV LANG=en_US.UTF-8
ENV LC_ALL=en_US.UTF-8

RUN <<EOF
# Create directory for dependencies
mkdir /deps
chown -R olympia:olympia /deps


# For backwards-compatibility purposes, set up links to uwsgi. Note that
# the target does not exist yet at this point, but it will later.
ln -s /deps/bin/uwsgi /usr/bin/uwsgi
ln -s ${DEPS_DIR}/bin/uwsgi /usr/bin/uwsgi
ln -s /usr/bin/uwsgi /usr/sbin/uwsgi

# Create the storage directory and the test file to verify nginx routing
mkdir -p ${HOME}/storage
chown -R olympia:olympia ${HOME}/storage
EOF

USER olympia:olympia

ENV PIP_USER=true
ENV PIP_BUILD=/deps/build/
ENV PIP_CACHE_DIR=/deps/cache/
ENV PIP_SRC=/deps/src/
ENV PYTHONUSERBASE=/deps
ENV PIP_BUILD=${DEPS_DIR}/build/
ENV PIP_CACHE_DIR=${DEPS_DIR}/cache/
ENV PIP_SRC=${DEPS_DIR}/src/
ENV PYTHONUSERBASE=${DEPS_DIR}
ENV PATH=$PYTHONUSERBASE/bin:$PATH
ENV NPM_CONFIG_PREFIX=/deps/
ENV NPM_CACHE_DIR=/deps/cache/npm
ENV NPM_CACHE_DIR=${DEPS_DIR}/cache/npm
ENV NPM_DEBUG=true
# Set python path to the project root and src to resolve olympia modules correctly
ENV PYTHONPATH=${HOME}:${HOME}/src

ENV PIP_COMMAND="python3 -m pip"
ENV NPM_ARGS="--prefix ${NPM_CONFIG_PREFIX} --cache ${NPM_CACHE_DIR} --loglevel verbose"
ENV NPM_ARGS="--cache ${NPM_CACHE_DIR} --loglevel verbose"

# All we need in "base" is pip to be installed
#this let's other layers install packages using the correct version.
Expand Down Expand Up @@ -135,8 +137,8 @@ RUN \
# Files required to install pip dependencies
--mount=type=bind,source=./requirements/prod.txt,target=${HOME}/requirements/prod.txt \
# Files required to install npm dependencies
--mount=type=bind,source=package.json,target=/deps/package.json \
--mount=type=bind,source=package-lock.json,target=/deps/package-lock.json \
--mount=type=bind,source=package.json,target=${HOME}/package.json \
--mount=type=bind,source=package-lock.json,target=${HOME}/package-lock.json \
# Mounts for caching dependencies
--mount=type=cache,target=${PIP_CACHE_DIR},uid=${OLYMPIA_UID},gid=${OLYMPIA_UID} \
--mount=type=cache,target=${NPM_CACHE_DIR},uid=${OLYMPIA_UID},gid=${OLYMPIA_UID} \
Expand Down Expand Up @@ -176,6 +178,8 @@ RUN \
--mount=type=bind,src=src,target=${HOME}/src \
--mount=type=bind,src=Makefile-docker,target=${HOME}/Makefile-docker \
--mount=type=bind,src=manage.py,target=${HOME}/manage.py \
--mount=type=bind,src=package.json,target=${HOME}/package.json \
--mount=type=bind,src=package-lock.json,target=${HOME}/package-lock.json \
<<EOF
echo "from olympia.lib.settings_base import *" > settings_local.py
DJANGO_SETTINGS_MODULE="settings_local" make -f Makefile-docker update_assets
Expand All @@ -194,4 +198,4 @@ COPY --from=info ${BUILD_INFO} ${BUILD_INFO}
# Copy compiled locales from builder
COPY --from=locales --chown=olympia:olympia ${HOME}/locale ${HOME}/locale
# Copy dependencies from `pip_production`
COPY --from=pip_production --chown=olympia:olympia /deps /deps
COPY --from=pip_production --chown=olympia:olympia ${DEPS_DIR} ${DEPS_DIR}
30 changes: 5 additions & 25 deletions Makefile-docker
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,6 @@ export PYTHON_COMMAND=python3
export PIP_COMMAND=$(PYTHON_COMMAND) -m pip
APP=src/olympia/

NODE_MODULES := $(NPM_CONFIG_PREFIX)node_modules/

REQUIRED_FILES := \
Makefile \
Makefile-os \
Makefile-docker \
/deps/package.json \
/deps/package-lock.json \
/addons-server-docker-container \

# Build list of dependencies to install
DEPS = pip prod
# If we're running a development image, then we should install the development dependencies
Expand All @@ -39,16 +29,6 @@ check_pip_packages: ## check the existence of multiple python packages
./scripts/check_pip_packages.sh $$dep.txt; \
done

.PHONY: check_files
check_files: ## check the existence of multiple files
@for file in $(REQUIRED_FILES); do test -f "$$file" || (echo "$$file is missing." && exit 1); done
@echo "All required files are present."

.PHONY: check_olympia_user
check_olympia_user: ## check if the olympia user exists and is current user
@if [ "$$(id -u olympia)" != "$$(id -u)" ]; then echo "The current user is not the olympia user."; exit 1; fi
@echo "The current user is the olympia user."

.PHONY: check_django
check_django: ## check if the django app is configured properly
./manage.py check
Expand All @@ -61,7 +41,7 @@ check_nginx: ## check if the nginx config for local development is configured p
@echo "Nginx user-media configuration looks correct."

.PHONY: check
check: check_nginx check_files check_olympia_user check_debian_packages check_pip_packages check_django
check: check_nginx check_debian_packages check_pip_packages check_django

.PHONY: data_dump
data_dump:
Expand Down Expand Up @@ -94,7 +74,7 @@ setup-ui-tests:
lint: ## lint the code
ruff check .
ruff format --check .
NODE_PATH=$(NODE_MODULES) npm exec $(NPM_ARGS) -- prettier --check '**'
npm exec $(NPM_ARGS) -- prettier --check '**'
curlylint src/

lint-codestyle: lint
Expand Down Expand Up @@ -199,15 +179,15 @@ test_failed: ## rerun the failed tests from the previous run

.PHONY: run_js_tests
run_js_tests: ## Run the JavaScript test suite (requires compiled/compressed assets).
NODE_PATH=$(NODE_MODULES) npm exec $(NPM_ARGS) -- jest tests/js
npm exec $(NPM_ARGS) -- jest tests/js

.PHONY: watch_js_tests
watch_js_tests: ## Run+watch the JavaScript test suite (requires compiled/compressed assets).
NODE_PATH=$(NODE_MODULES) npm exec $(NPM_ARGS) -- jest --watch
npm exec $(NPM_ARGS) -- jest --watch

.PHONY: format
format: ## Autoformat our codebase.
NODE_PATH=$(NODE_MODULES) npm exec $(NPM_ARGS) -- prettier --write '**'
npm exec $(NPM_ARGS) -- prettier --write '**'
ruff check --fix-only .
ruff format .

Expand Down
3 changes: 1 addition & 2 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,6 @@ services:
]
volumes:
- ${HOST_MOUNT_SOURCE:?}:/data/olympia
- ${HOST_MOUNT_SOURCE:?}deps:/deps
- ${HOST_MOUNT_SOURCE:?}storage:/data/olympia/storage
extra_hosts:
- "olympia.test:127.0.0.1"
Expand Down Expand Up @@ -104,7 +103,7 @@ services:
volumes:
- data_nginx:/etc/nginx/conf.d
- ${HOST_MOUNT_SOURCE:?}:/srv
- data_site_static:/srv/site-static
- data_site_static:/srv/static
- ${HOST_MOUNT_SOURCE:?}storage:/srv/storage
ports:
- "80:80"
Expand Down
6 changes: 3 additions & 3 deletions docker/entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,10 @@ fi
NEW_HOST_UID=$(get_olympia_uid)
OLYMPIA_ID_STRING="${NEW_HOST_UID}:$(get_olympia_gid)"

# If we are on production mode, update the ownership of /data/olympia and /deps to match the new id
# If we are on production mode, update the ownership of /data/olympia to match the new id
if [[ "${HOST_MOUNT}" == "production" ]]; then
echo "Updating ownership of /data/olympia and /deps to ${OLYMPIA_ID_STRING}"
chown -R ${OLYMPIA_ID_STRING} /data/olympia /deps
echo "Updating ownership of ${HOME} to ${OLYMPIA_ID_STRING}"
chown -R ${OLYMPIA_ID_STRING} ${HOME}
fi

cat <<EOF | su -s /bin/bash $OLYMPIA_USER
Expand Down
11 changes: 5 additions & 6 deletions docker/nginx/addons.conf
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,9 @@ server {
}

location /static/ {
alias /srv/static/;

# Fallback to the uwsgi server if the file is not found in the static files directory.
# This will happen for vendor files from pytnon or npm dependencies that won't be available
# in the static files directory.
error_page 404 = @olympia;
root /srv;
try_files $uri @olympia;
add_header X-Served-By "nginx-direct" always;
}

location /user-media/ {
Expand Down Expand Up @@ -50,6 +47,8 @@ server {
uwsgi_param X-Real-IP $remote_addr;
uwsgi_param X-Forwarded-For $proxy_add_x_forwarded_for;
uwsgi_param X-Forwarded-Protocol ssl;

add_header X-Served-By "olympia" always;
}

location @frontendamo {
Expand Down
2 changes: 1 addition & 1 deletion docs/topics/development/building_and_running_services.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ The Dockerfile for the **addons-server** project uses a multi-stage build to opt

3. **Mounts in Docker Compose**:
- **Mounting Local Repository**: The volume `.:/data/olympia` mounts the local Git repository into the container, allowing real-time changes to files within the container.
- **Mounting Dependencies**: The volume `./deps:/deps` mounts the dependencies directory, enabling better caching across builds and providing visibility for debugging directly on the host.
- **Mounting Dependencies**: The volume `./deps:/data/olympia/deps` mounts the dependencies directory, enabling better caching across builds and providing visibility for debugging directly on the host.

4. **Environment Variables for OLYMPIA_USER**:
- **Development Setup**: The `OLYMPIA_UID` .env variable is set to the host user ID, ensuring that the container runs with the correct permissions.
Expand Down
16 changes: 7 additions & 9 deletions docs/topics/development/dependency_management.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ Managing dependencies effectively is crucial for maintaining a stable and consis

## Python Dependencies

Python dependencies are managed using the Makefile and requirements files. All dependencies are installed into the `/deps` directory, which centralizes dependency management and simplifies data mounts.
Python dependencies are managed using the Makefile and requirements files. All dependencies are installed into the `/data/olympia/deps` directory, which centralizes dependency management and simplifies data mounts.

- **Environment Variables**: The project sets environment variables for Python CLIs to install dependencies in specific locations. This includes setting paths for `PIP_CACHE_DIR`, `PIP_SRC`, and others to use the `/deps` directory.
- **Environment Variables**: The project sets environment variables for Python CLIs to install dependencies in specific locations. This includes setting paths for `PIP_CACHE_DIR`, `PIP_SRC`, and others to use the `/data/olympia/deps` directory.

- **Caching Mechanism**: By using Docker build stages, the project isolates the stages responsible for installing dependencies. This prevents these stages from re-running unless the actual dependency files are changed. Additionally, internal Python cache folders are cached, avoiding unnecessary re-downloads of packages and saving time and bandwidth.

Expand Down Expand Up @@ -66,9 +66,7 @@ Ensure to comment in the requirements file above transitive dependencies which d

## Node.js Dependencies

Node.js dependencies are managed using npm. Similar to Python dependencies, Node.js dependencies are installed into the `/deps` directory.

- **Environment Variables**: Environment variables are set for Node.js CLIs to ensure that dependencies are installed in the `/deps` directory. This includes setting paths for `NPM_CONFIG_PREFIX` and `NPM_CACHE_DIR`.
Node.js dependencies are managed using npm.

- **Caching Mechanism**: Node.js dependencies are also cached using Docker build stages. Internal npm cache folders are cached to avoid re-downloading packages unnecessarily.

Expand All @@ -86,19 +84,19 @@ NPM is a fully-featured package manager, so you can use the standard CLI.

The Dockerfile uses build stages to isolate the dependency installation process. This ensures that stages do not re-run unless the dependency files themselves change. The caching mechanism includes:

- **Dependency Cache**: Both Python and Node.js dependencies are cached in the `/deps` directory.
- **Dependency Cache**: Both Python and Node.js dependencies are cached in the `/data/olympia/deps` directory.
- **Cache Folders**: Internal cache folders for pip and npm are themselves cached to speed up the build process.

## GitHub Actions Cache

The project uses a custom GitHub Actions action (`./.github/actions/cache-deps`) to cache the `/deps` folder. This action significantly increases install times for CI runs by leveraging the GitHub Actions cache.
The project uses a custom GitHub Actions action (`./.github/actions/cache-deps`) to cache the `/data/olympia/deps` folder. This action significantly increases install times for CI runs by leveraging the GitHub Actions cache.

```yaml
- name: Cache dependencies
uses: ./.github/actions/cache-deps
```
By caching the `/deps` folder, the project ensures that dependencies are quickly restored in CI environments, reducing overall build and test times.
By caching the `/data/olympia/deps` folder, the project ensures that dependencies are quickly restored in CI environments, reducing overall build and test times.

## Updating/Installing Dependencies

Expand All @@ -109,4 +107,4 @@ make up
```

This will rebuild the Docker image with the current dependencies specified in the `requirements` and `package.json` files.
We do not support updating dependencies in a running container as the /deps folder is not writable by the olympia user.
We do not support updating dependencies in a running container as the /data/olympia/deps folder is not writable by the olympia user.
2 changes: 1 addition & 1 deletion docs/topics/development/performance_and_optimization.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ Docker layer caching is a powerful feature that significantly speeds up the buil
export DOCKER_BUILDKIT=1
```

- **GitHub Actions Cache**: The custom action (`./.github/actions/cache-deps`) caches the `/deps` folder, leveraging GitHub Actions cache to improve CI run times.
- **GitHub Actions Cache**: The custom action (`./.github/actions/cache-deps`) caches the `/data/olympia/deps` folder, leveraging GitHub Actions cache to improve CI run times.

## Performance Testing

Expand Down
4 changes: 4 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,12 @@ classifiers = [

[tool.ruff]
exclude = [
"deps",
"node_modules",
"docs",
"site-static",
"static",
"static-build",
".git",
"*/migrations/*.py",
]
Expand Down
31 changes: 16 additions & 15 deletions scripts/install_deps.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,14 @@
import sys


def copy_package_json():
"""Copy package.json files to deps directory if they exist."""
try:
shutil.copy('/data/olympia/package.json', '/deps')
shutil.copy('/data/olympia/package-lock.json', '/deps')
except (IOError, OSError):
pass # Ignore if files don't exist or can't be copied
def clean_dir(dir_path, filter):
if not os.path.exists(dir_path):
return

for item in os.listdir(dir_path):
item_path = os.path.join(dir_path, item)
if os.path.isdir(item_path) and item not in filter:
shutil.rmtree(item_path)


def main(targets):
Expand All @@ -21,6 +22,8 @@ def main(targets):
DOCKER_TAG = os.environ.get('DOCKER_TAG', 'local')
DOCKER_TARGET = os.environ.get('DOCKER_TARGET', '')
OLYMPIA_DEPS = os.environ.get('OLYMPIA_DEPS', '')
DEPS_DIR = os.environ.get('DEPS_DIR')
NPM_DEPS_DIR = os.environ.get('NPM_DEPS_DIR')

if not targets:
raise ValueError('No targets specified')
Expand All @@ -31,23 +34,21 @@ def main(targets):
f'DOCKER_TAG: {DOCKER_TAG} \n',
f'DOCKER_TARGET: {DOCKER_TARGET} \n',
f'OLYMPIA_DEPS: {OLYMPIA_DEPS} \n',
f'DEPS_DIR: {DEPS_DIR} \n',
f'NPM_DEPS_DIR: {NPM_DEPS_DIR} \n',
)

# If we are installing production dependencies or on a non local image
# we always remove existing deps as we don't know what was previously
# installed or in the host ./deps directory before running this script
# installed or in the host ./deps or ./node_modules directory
# before running this script
if 'local' not in DOCKER_TAG or OLYMPIA_DEPS == 'production':
print('Removing existing deps')
for item in os.listdir('/deps'):
item_path = os.path.join('/deps', item)
if os.path.isdir(item_path) and item != 'cache':
shutil.rmtree(item_path)
clean_dir(DEPS_DIR, ['cache'])
clean_dir(NPM_DEPS_DIR, [])
else:
print('Updating existing deps')

# Copy package.json files
copy_package_json()

# Prepare the includes lists
pip_includes = []
npm_includes = []
Expand Down
Loading

0 comments on commit b712f84

Please sign in to comment.