Skip to content

Commit

Permalink
Migrate dependencies to /data/olympia
Browse files Browse the repository at this point in the history
- Updated docker-compose.yml to mount dependencies in /data/olympia/deps for better organization.
- Modified Dockerfile to set environment variables for dependency directories and ensure proper ownership.
- Adjusted Makefile-docker to remove NODE_MODULES variable and streamline npm commands.
- Updated documentation to reflect changes in dependency paths.
- Refactored install_deps.py to clean up dependency directories and removed obsolete package.json copying logic.
- Updated settings_base.py to reference new dependency paths.
  • Loading branch information
KevinMind committed Jan 27, 2025
1 parent 6cc53d9 commit 3d56f05
Show file tree
Hide file tree
Showing 12 changed files with 77 additions and 107 deletions.
48 changes: 25 additions & 23 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 All @@ -127,16 +129,16 @@ COPY docker/etc/mime.types /etc/mime.types

# Define production dependencies as a single layer
# let's the rest of the stages inherit prod dependencies
# and makes copying the /deps dir to the final layer easy.
# and makes copying the /data/olympia/deps dir to the final layer easy.
FROM base AS pip_production

RUN \
--mount=type=bind,source=scripts/install_deps.py,target=${HOME}/scripts/install_deps.py \
# 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 @@ -197,4 +199,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}
18 changes: 4 additions & 14 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 Down Expand Up @@ -88,7 +78,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 @@ -193,15 +183,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
4 changes: 2 additions & 2 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ services:
volumes:
# used by: web, worker, nginx
- ${HOST_MOUNT_SOURCE:?}:/data/olympia
- ${HOST_MOUNT_SOURCE:?}deps:/deps
- ${HOST_MOUNT_SOURCE:?}deps:/data/olympia/deps
- data_site_static:/data/olympia/site-static
- ${HOST_MOUNT_SOURCE:?}storage:/data/olympia/storage
worker:
Expand All @@ -64,7 +64,7 @@ services:
]
volumes:
- ${HOST_MOUNT_SOURCE:?}:/data/olympia
- ${HOST_MOUNT_SOURCE:?}deps:/deps
- ${HOST_MOUNT_SOURCE:?}deps:/data/olympia/deps
- ${HOST_MOUNT_SOURCE:?}storage:/data/olympia/storage
extra_hosts:
- "olympia.test:127.0.0.1"
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 /data/olympia to ${OLYMPIA_ID_STRING}"
chown -R ${OLYMPIA_ID_STRING} /data/olympia
fi

cat <<EOF | su -s /bin/bash $OLYMPIA_USER
Expand Down
4 changes: 2 additions & 2 deletions 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 Expand Up @@ -178,4 +178,4 @@ Additionally this volume is "external" to allow the volume to persist across con
We additionally mount serval local directories to the web/worker containers.

- **.:/data/olympia**: Mounts the local repository into the container to allow real-time changes to files within the container.
- **./deps:/deps**: Mounts the dependencies directory to enable better caching across builds and provide visibility for debugging directly on the host.
- **./deps:/data/olympia/deps**: Mounts the dependencies directory to enable better caching across builds and provide visibility for debugging directly on the host.
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
2 changes: 2 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ classifiers = [

[tool.ruff]
exclude = [
"deps",
"node_modules",
"docs",
"static",
".git",
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
6 changes: 3 additions & 3 deletions src/olympia/amo/fixtures/sentry_event.json
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@
"stacktrace": {
"frames": [
{
"abs_path": "/deps/lib/python3.9/site-packages/django/core/handlers/exception.py",
"abs_path": "/data/olympia/deps/lib/python3.9/site-packages/django/core/handlers/exception.py",
"context_line": " response = get_response(request)",
"filename": "django/core/handlers/exception.py",
"function": "inner",
Expand Down Expand Up @@ -142,7 +142,7 @@
}
},
{
"abs_path": "/deps/lib/python3.9/site-packages/django/core/handlers/base.py",
"abs_path": "/data/olympia/deps/lib/python3.9/site-packages/django/core/handlers/base.py",
"context_line": " response = wrapped_callback(request, *callback_args, **callback_kwargs)",
"filename": "django/core/handlers/base.py",
"function": "_get_response",
Expand Down Expand Up @@ -175,7 +175,7 @@
}
},
{
"abs_path": "/deps/lib/python3.9/site-packages/django/views/decorators/cache.py",
"abs_path": "/data/olympia/deps/lib/python3.9/site-packages/django/views/decorators/cache.py",
"context_line": " response = view_func(request, *args, **kwargs)",
"filename": "django/views/decorators/cache.py",
"function": "_wrapped_view_func",
Expand Down
12 changes: 6 additions & 6 deletions src/olympia/lib/settings_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,15 +98,15 @@ def path(*folders):
# LESS CSS OPTIONS (Debug only).
LESS_PREPROCESS = True # Compile LESS with Node, rather than client-side JS?
LESS_LIVE_REFRESH = False # Refresh the CSS on save?
LESS_BIN = env('LESS_BIN', default='/deps/node_modules/less/bin/lessc')
LESS_BIN = env('LESS_BIN', default=path('node_modules/less/bin/lessc'))

# Path to cleancss (our CSS minifier).
CLEANCSS_BIN = env(
'CLEANCSS_BIN', default='/deps/node_modules/clean-css-cli/bin/cleancss'
'CLEANCSS_BIN', default=path('node_modules/clean-css-cli/bin/cleancss')
)

# Path to our JS minifier.
JS_MINIFIER_BIN = env('JS_MINIFIER_BIN', default='/deps/node_modules/terser/bin/terser')
JS_MINIFIER_BIN = env('JS_MINIFIER_BIN', default=path('node_modules/terser/bin/terser'))

# rsvg-convert is used to save our svg static theme previews to png
RSVG_CONVERT_BIN = env('RSVG_CONVERT_BIN', default='rsvg-convert')
Expand All @@ -116,7 +116,7 @@ def path(*folders):

# Path to our addons-linter binary
ADDONS_LINTER_BIN = env(
'ADDONS_LINTER_BIN', default='/deps/node_modules/addons-linter/bin/addons-linter'
'ADDONS_LINTER_BIN', default=path('node_modules/addons-linter/bin/addons-linter')
)
# --enable-background-service-worker linter flag value
ADDONS_LINTER_ENABLE_SERVICE_WORKER = False
Expand Down Expand Up @@ -1327,8 +1327,8 @@ def read_only_mode(env):
'django_node_assets.finders.NodeModulesFinder',
)

NODE_MODULES_ROOT = os.path.join('/', 'deps', 'node_modules')
NODE_PACKAGE_JSON = os.path.join('/', 'deps', 'package.json')
NODE_MODULES_ROOT = path('node_modules')
NODE_PACKAGE_JSON = path('package.json')
NODE_PACKAGE_MANAGER_INSTALL_OPTIONS = ['--dry-run']

STATIC_BUILD_PATH = path('static-build')
Expand Down
Loading

0 comments on commit 3d56f05

Please sign in to comment.