Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add docker dev environment #28

Merged
merged 1 commit into from
Nov 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions .devcontainer/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# This should be kept in sync with the python version used in docker/Dockerfile and
# docker/images/fakesentry/Dockerfile
FROM --platform=linux/amd64 mcr.microsoft.com/devcontainers/python:3.11-bullseye@sha256:105bf6a63ab025206f019a371a735fec6553db0be520030c7a2fd0e002947232

ARG userid=10001
ARG groupid=10001

WORKDIR /app

# add a non-privileged user for installing and running the application
# We use --non-unique in case $groupid/$userid collide with the existing "vscode" user.
# useradd -g app --uid $userid --non-unique --shell /usr/sbin/nologin --create-home app && \
RUN groupadd --gid $groupid --non-unique app && \
useradd -g app --uid $userid --non-unique --shell /bin/bash --create-home app && \
chown app:app /app/

# Install Debian packages
RUN apt-get update && \
apt-get install -y ripgrep tig

# Install Python dependencies
COPY requirements.txt /app/
RUN pip install -U 'pip>=20' && \
pip install --no-cache-dir --no-deps --only-binary :all: -r requirements.txt && \
pip install --no-cache-dir ipython && \
pip check --disable-pip-version-check
28 changes: 28 additions & 0 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"name": "Obs Common",
"dockerComposeFile": [
"../docker-compose.yml"
],
"service": "devcontainer",
"runServices": [
"devcontainer"
],
"shutdownAction": "none",
"workspaceFolder": "/app",
"customizations": {
"vscode": {
"settings": {
"python.testing.unittestEnabled": false,
"python.testing.pytestEnabled": true,
"[python]": {
"editor.defaultFormatter": "charliermarsh.ruff"
}
},
"extensions": [
"charliermarsh.ruff"
]
}
},
"remoteUser": "app",
"updateRemoteUserUID": false
}
11 changes: 11 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
*.py[co]
*.sw[po]
.docker-build
.DS_Store
.env
.pytest_cache
.python-version
build
dist/
obs_common.egg-info/
venv/
15 changes: 6 additions & 9 deletions .github/workflows/build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ jobs:
run: |
python -m venv venv
venv/bin/pip install -r requirements.txt
venv/bin/pip install -e . --no-deps
- name: Verify requirements.txt
if: ${{ matrix.python-version == '3.11' }}
run: |
Expand All @@ -34,22 +35,18 @@ jobs:
- name: Run lint check
if: ${{ matrix.python-version == '3.11' }}
run: |
venv/bin/ruff format --check obs_common tests
venv/bin/ruff check obs_common tests
PATH="venv/bin:$PATH" bin/lint.sh
- name: Run tests
env:
SENTRY_DSN: http://public@localhost:8090/1
STORAGE_EMULATOR_HOST: http://localhost:8001
PUBSUB_EMULATOR_HOST: localhost:5010
run: |
docker compose up -d fakesentry gcs-emulator
venv/bin/pytest tests/
docker compose up -d fakesentry gcs-emulator pubsub
# Run outside docker because we are testing the matrix python version
PATH="venv/bin:$PATH" bin/test.sh
Comment on lines +46 to +47

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah. At first I was wondering "what is The Matrix TM", but I see we are running the tests against multiple versions of Python:

python-version: ["3.11", "3.12"]
.

# stop services immediate and ignore errors
docker compose down -t0 || true
- name: License Check
if: ${{ matrix.python-version == '3.11' }}
run: |
venv/bin/pip install -e . --no-deps
venv/bin/license-check

build-and-release:
permissions:
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
*.py[co]
*.sw[po]
.docker-build
.DS_Store
.env
.pytest_cache
.python-version
build
Expand Down
55 changes: 55 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at https://mozilla.org/MPL/2.0/.

# Include .env and export it so variables set in there are available
# in Makefile.
include .env
export

.DEFAULT_GOAL := help
.PHONY: help
help:
@echo "Usage: make RULE"
@echo ""
@grep -E '^[a-zA-Z0-9_-]+:.*?## .*$$' Makefile \
| grep -v grep \
| sed -n 's/^\(.*\): \(.*\)##\(.*\)/\1\3/p' \
| column -t -s '|'
@echo ""
@echo "Adjust your .env file to set configuration."

.docker-build:
make build

.env:
touch .env

.PHONY: build
build: .env ## | Build docker images.
docker compose --progress plain build
touch .docker-build

.PHONY: shell
shell: .env .docker-build ## | Open a shell in docker.
docker compose run --rm shell

.PHONY: devcontainer
devcontainer: .env .docker-build ## | Run VS Code development container.
docker compose up --detach devcontainer

.PHONY: rebuildreqs
rebuildreqs: .env .docker-build ## | Rebuild requirements.txt file after requirements.in changes.
docker compose run --rm --no-deps shell pip-compile --allow-unsafe --generate-hashes --strip-extras --quiet

.PHONY: lint
lint: .env .docker-build ## | Lint code.
docker compose run --rm --no-deps shell bin/lint.sh

.PHONY: lintfix
lintfix: .env .docker-build ## | Reformat code.
docker compose run --rm --no-deps shell bin/lint.sh --fix

.PHONY: test
test: .env .docker-build ## | Run tests.
docker compose run --rm shell bin/test.sh
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As a side note, I've been switching all my personal projects to using just:

https://just.systems/

https://github.com/willkg/everett/blob/main/justfile

It adds another requirement, but it's easier to use and better at doing the things I was using Makefiles for. I don't know whether it's easy to install/use on macOS or whether it's a viable replacement for make for our purposes. But I figured I'd mention it.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks awesome, i'd love to switch. I think we should decide as a team and make the change more broadly than only this repo, so i'll bring it up in the meeting on tuesday

39 changes: 39 additions & 0 deletions bin/lint.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
#!/bin/bash

# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at https://mozilla.org/MPL/2.0/.

# Usage: bin/run_lint.sh [--fix]
#
# Runs linting and code fixing.
#
# This should be called from inside a container or venv.

set -euo pipefail

FILES="bin obs_common tests"
PYTHON_VERSION=$(python --version)


if [[ "${1:-}" == "--fix" ]]; then
echo ">>> ruff fix (${PYTHON_VERSION})"
ruff format $FILES
ruff check --fix $FILES

else
echo ">>> ruff (${PYTHON_VERSION})"
ruff check $FILES
ruff format --check $FILES

echo ">>> license check (${PYTHON_VERSION})"
if [[ -d ".git" ]]; then
# If the .git directory exists, we can let license-check do
# git ls-files.
license-check
else
# The .git directory doesn't exist, so run it on all the Python
# files in the tree.
license-check .
fi
fi
27 changes: 27 additions & 0 deletions bin/test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#!/bin/bash

# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at https://mozilla.org/MPL/2.0/.

# Usage: bin/run_tests.sh
#
# Runs tests.
#
# This should be called from inside a container and after the dependent
# services have been launched. It depends on:
Comment on lines +11 to +12

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this comment apply anymore if we're running this outside of a container per the "Run tests" CI job in .github/workflows/build.yaml?

I see make test still runs it from a shell in a Docker container. And the environment variables are using docker aliases for the host names... Now I'm wondering how the above CI is passing still in that case. I guess we set different env vars in that context... Ah I see that we do:

env:
SENTRY_DSN: http://public@localhost:8090/1
STORAGE_EMULATOR_HOST: http://localhost:8001
PUBSUB_EMULATOR_HOST: localhost:5010

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, anyone running this manually should do so inside the docker container. CI runs it outside of the docker container so that it can test multiple python versions.

#
# * elasticsearch
# * postgresql

set -euo pipefail

# Wait for services to be ready (both have the same endpoint url)
echo ">>> wait for services"
urlwait "http://${PUBSUB_EMULATOR_HOST}" 10
urlwait "${STORAGE_EMULATOR_HOST}/storage/v1/b" 10
waitfor --verbose --codes=200,404 "${SENTRY_DSN}"

# Run tests
echo ">>> pytest"
pytest $@
36 changes: 35 additions & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,43 @@
services:
shell:
build:
context: .
dockerfile: docker/Dockerfile
args:
userid: ${USE_UID:-10001}
groupid: ${USE_GID:-10001}
image: local/obs-common-shell
env_file:
- docker/config/local_dev.env
links:
- fakesentry
- gcs-emulator
- pubsub
volumes:
- .:/app

devcontainer:
build:
dockerfile: .devcontainer/Dockerfile
args:
userid: ${USE_UID:-10001}
groupid: ${USE_GID:-10001}
image: local/obs-common-devcontainer
entrypoint: ["sleep", "inf"]
env_file:
- docker/config/local_dev.env
links:
- fakesentry
- gcs-emulator
- pubsub
volumes:
- .:/app

# https://github.com/willkg/kent
fakesentry:
build:
context: docker/images/fakesentry
image: local/tecken_fakesentry
image: local/obs-common-fakesentry
ports:
- "${EXPOSE_SENTRY_PORT:-8090}:8090"
command: run --host 0.0.0.0 --port 8090
Expand Down
35 changes: 35 additions & 0 deletions docker/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# This should be kept in sync with the python version used in .devcontainer/Dockerfile and
# docker/images/fakesentry/Dockerfile
FROM --platform=linux/amd64 python:3.11.10-slim-bullseye@sha256:f6a64ef0a5cc14855b15548056a8fc77f4c3526b79883fa6709a8e23f676ac34

# Set up user and group
ARG groupid=10001
ARG userid=10001

WORKDIR /app/
RUN groupadd --gid $groupid app && \
useradd -g app --uid $userid --shell /usr/sbin/nologin --create-home app && \
chown app:app /app/

RUN apt-get update && \
apt-get install -y --no-install-recommends \
git \
ripgrep \
tig && \
apt-get autoremove -y && \
apt-get clean && \
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*

# Install Python dependencies
COPY requirements.txt /app/

RUN pip install -U 'pip>=20' && \
pip install --no-cache-dir --no-deps --only-binary :all: -r requirements.txt && \
pip install --no-cache-dir ipython && \
pip check --disable-pip-version-check

COPY . /app

RUN pip install -e . --no-deps

CMD ["/bin/bash"]
8 changes: 8 additions & 0 deletions docker/config/local_dev.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Set Pub/Sub library to use emulator
PUBSUB_EMULATOR_HOST=pubsub:5010

# Set GCS library to use emulator
STORAGE_EMULATOR_HOST=http://gcs-emulator:8001

# Set up fakesentry
SENTRY_DSN=http://public@fakesentry:8090/1
2 changes: 2 additions & 0 deletions obs_common/waitfor.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ def main(args=None):
if resp.code in ok_codes:
sys.exit(0)
last_fail = f"HTTP status code: {resp.code}"
except ConnectionResetError as error:
last_fail = f"ConnectionResetError: {error}"
except TimeoutError as error:
last_fail = f"TimeoutError: {error}"
except urllib.error.URLError as error:
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ service-status = "obs_common.service_status:main"
gcs-cli = "obs_common.gcs_cli:main"
pubsub-cli = "obs_common.pubsub_cli:main"
sentry-wrap = "obs_common.sentry_wrap:cli_main"
waitfor = "obs_common.waitfor:main"

[build-system]
requires = ["setuptools", "setuptools_scm[toml]>=6.2"]
Expand Down
1 change: 1 addition & 0 deletions requirements.in
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ requests==2.32.3
ruff==0.7.1
sentry-sdk==2.17.0
twine==5.1.1
urlwait==1.0
4 changes: 4 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -640,6 +640,10 @@ urllib3==2.2.2 \
# requests
# sentry-sdk
# twine
urlwait==1.0 \
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we have waitfor, we can build into it everything we're using urlwait for. urlwait is pmac's, but I don't think it's maintained anymore.

--hash=sha256:a9bf2da792fa6983fa93f6360108e16615066ab0f9cfb7f53e5faee5f5dffaac \
--hash=sha256:eae2c20001efc915166cac79c04bac0088ad5787ec64b36f27afd2f359953b2b
# via -r requirements.in
wheel==0.44.0 \
--hash=sha256:2376a90c98cc337d18623527a97c31797bd02bad0033d41547043a1cbfbe448f \
--hash=sha256:a29c3f2817e95ab89aa4660681ad547c0e9547f20e75b0562fe7723c9a2a9d49
Expand Down