Skip to content
This repository was archived by the owner on Mar 10, 2021. It is now read-only.

Commit 6d940f3

Browse files
authored
chore: Add Makefile and update Readme (#5)
1 parent 648930a commit 6d940f3

16 files changed

+348
-38
lines changed

.flake8

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
2+
[flake8]
3+
# Explicitly ignore rules to maintain better compatibility with black.
4+
#
5+
# F403: import *
6+
# F811: redefinition of unused `name` from line `N`
7+
# F821: undefined name
8+
# F841: local variable assigned but never used
9+
# E402: module level import not at top of file
10+
# I100: your import statements are in the wrong order.
11+
# I101: the names in your from import are in the wrong order.
12+
# D400: first line should end with a period.
13+
# E203: colons should not have any space before them.
14+
# E231: missing whitespace after ','
15+
# E501: line lengths are recommended to be no greater than 79 characters.
16+
# E503: there is no need for backslashes between brackets.
17+
# E731: do not assign a lambda expression, use a def
18+
# W293: line break before binary operator
19+
# W293: blank line contains whitespace
20+
select = E,F,W,C
21+
ignore = F403, F811, F821, F841, E231, E402, I100, I101, D400, E501, E501, E503, E731, W293, W503
22+
extend-ignore = E203
23+
max-complexity = 15
24+
# https://docs.djangoproject.com/en/dev/internals/contributing/writing-code/coding-style/
25+
max-line-length = 119
26+
builtins = c, get_config
27+
exclude =
28+
.ansible,
29+
.cache,
30+
__pycache__,
31+
.github,
32+
.ipynb_checkpoints,
33+
.pytest_cache,
34+
.travis,
35+
.vscode,
36+
ansible,
37+
docs,
38+
node_modules,
39+
venv

.hadolint.yml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
ignored:
3+
- DL3000
4+
- DL3004
5+
- DL3006
6+
- DL3007
7+
- DL3008
8+
- DL3015
9+
- DL3016
10+
- DL4006
11+
- SC2035

.travis.yml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
---
2+
dist: bionic
3+
language: python
4+
python:
5+
- 3.8
6+
sudo: required
7+
services:
8+
- docker
9+
10+
jobs:
11+
include:
12+
- stage: full-test
13+
install:
14+
- pip install --upgrade pip
15+
- make venv
16+
script:
17+
- set -e
18+
- make build/base-notebook
19+
- make test
20+
21+
stages:
22+
- name: full-test
23+
if: branch = main
24+

MANIFEST.in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
include LICENSE

Makefile

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
# inspiration from https://github.com/jupyter/docker-stacks
2+
.PHONY: dev
3+
4+
# Use bash for inline if-statements in target
5+
SHELL:=bash
6+
TAG:=latest
7+
OWNER:=illumidesk
8+
VENV_NAME?=venv
9+
VENV_BIN=$(shell pwd)/${VENV_NAME}/bin
10+
VENV_ACTIVATE=. ${VENV_BIN}/activate
11+
12+
PYTHON=${VENV_BIN}/python3
13+
14+
# Linter
15+
HADOLINT="${HOME}/hadolint"
16+
17+
help:
18+
# credits:
19+
# http://marmelab.com/blog/2016/02/29/auto-documented-makefile.html
20+
# http://github.com/jupyter/docker-stacks
21+
@echo "illumidesk/jupyter-pgweb-proxy"
22+
@echo "====================="
23+
@echo "Run one of the commands below with the 'make ...' command, i.e. 'make dev'."
24+
@echo
25+
@grep -E '^[a-zA-Z0-9_%/-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
26+
27+
build: ## build the latest image for a stack
28+
${VENV_BIN}/jupyter-repo2docker --no-run --user-id 1000 --user-name jovyan --image-name $(OWNER)/pgweb:$(TAG) ./binder .
29+
@echo -n "Built image size: "
30+
@docker images $(OWNER)/pgweb:$(TAG) --format "{{.Size}}"
31+
32+
clean-all: ## remove built images and running containers (including those w/ exit status)
33+
@docker rm -f $(shell docker ps -aq)
34+
35+
dev: venv ## run one of the containers (stacks) on port 8889
36+
docker-compose -f binder/docker-compose.yaml up -d --build
37+
38+
dev-down:venv ## stop (down) the docker-compose services
39+
docker-compose -f binder/docker-compose.yaml down
40+
41+
lint: venv ## lint the dockerfile(s)
42+
@echo "Linting Dockerfiles with Hadolint in ..."
43+
@git ls-files --exclude='Dockerfile*' --ignored binder/docker-compose.yaml | grep -v ppc64 | xargs -L 1 $(HADOLINT) --config .hadolint.yml
44+
@echo "Linting with Hadolint done!"
45+
@echo "Linting tests with flake8 ..."
46+
${VENV_BIN}/flake8
47+
@echo "Linting with flake8 done!"
48+
@echo "Applying black updates to test files..."
49+
${VENV_BIN}/black .
50+
@echo "Source formatting with black done!"
51+
52+
lint-install: ## install hadolint
53+
@echo "Installing hadolint at $(HADOLINT) ..."
54+
@curl -sL -o $(HADOLINT) "https://github.com/hadolint/hadolint/releases/download/v1.18.0/hadolint-$(shell uname -s)-$(shell uname -m)"
55+
@chmod 700 $(HADOLINT)
56+
@echo "Hadolint nstallation done!"
57+
@$(HADOLINT) --version
58+
59+
test: ## test images as running containers
60+
${VENV_BIN}/pytest -v
61+
62+
venv: lint-install ## install hadolint and create virtual environment
63+
test -d $(VENV_NAME) || virtualenv -p python3 $(VENV_NAME)
64+
${PYTHON} -m pip install -r dev-requirements.txt
65+
${PYTHON} -m pip install --upgrade pip

README.md

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,18 +25,35 @@ This test requires you to have a database instance available as a public endpoin
2525
### Run locally with `docker-compose`
2626

2727
```bash
28-
docker build -t jupyter/pgweb .
29-
docker build -it --rm -p 8888:8888 jupyter/pgweb
28+
make dev
3029
```
3130

3231
1. Open your browser: http://localhost:8889
3332
1. Click the `pgweb` item from the `New` dropdown in `Jupyter Classic` or click on the green `pgweb` icon in the Notebooks section in `Jupyter Lab`.
3433
1. Connect with the `Scheme` or `Standard` option.
3534

35+
For example, with the `Scheme` option the string would look like so:
36+
3637
```
3738
postgres://postgres:postgres@testdb:5432/db?sslmode=disable
3839
```
3940

41+
### Cleanup
42+
43+
Stop services:
44+
45+
```bash
46+
make dev-down
47+
```
48+
49+
Remove images and running containers:
50+
51+
> **NOTE**: this will stop all running containers on the local, including those with the exit status.
52+
53+
```bash
54+
make clean-all
55+
```
56+
4057
## The Hard Way
4158

4259
### Create and Activate Environment

Dockerfile renamed to binder/Dockerfile

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,16 @@ RUN wget -q "https://github.com/sosedoff/pgweb/releases/download/v${PGWEB_VERSIO
1515

1616
# setup package, enable classic extension, build lab extension
1717
USER "${NB_USER}"
18-
COPY . "${HOME}"/
19-
COPY requirements.txt "${HOME}"/
2018
WORKDIR "${HOME}"
21-
RUN python3 -m pip install .
22-
RUN python3 -m pip install -r requirements.txt \
23-
&& jupyter serverextension enable --sys-prefix jupyter_server_proxy \
19+
RUN python3 -m pip install git+https://github.com/illumidesk/jupyter-pgweb-proxy.git
20+
RUN jupyter serverextension enable --sys-prefix jupyter_server_proxy \
2421
&& jupyter labextension install @jupyterlab/server-proxy \
2522
&& jupyter lab build
2623

2724
# copy configs, update permissions as root
2825
USER root
26+
RUN cp /etc/jupyter/jupyter_notebook_config.py /etc/jupyter/jupyter_notebook_config_base.py
27+
COPY jupyter_notebook_config.py /etc/jupyter/jupyter_notebook_config.py
2928
RUN fix-permissions /etc/jupyter
3029

3130
USER "${NB_USER}"
File renamed without changes.

jupyter_notebook_config.py renamed to binder/jupyter_notebook_config.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33

44
# load base config
5-
load_subconfig('/etc/jupyter/jupyter_notebook_config_base.py')
5+
load_subconfig("/etc/jupyter/jupyter_notebook_config_base.py")
66

77

88
# supports iframe and samesite cookies
@@ -11,6 +11,6 @@
1111
"cookie_options": {"SameSite": "None", "Secure": True},
1212
}
1313
c.NotebookApp.allow_root = True
14-
c.NotebookApp.allow_origin = '*'
15-
c.NotebookApp.token = ''
16-
c.NotebookApp.default_url = '/lab'
14+
c.NotebookApp.allow_origin = "*"
15+
c.NotebookApp.token = ""
16+
c.NotebookApp.default_url = "/lab"

conftest.py

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
# sourced from:
2+
# https://github.com/jupyter/docker-stacks
3+
#
4+
# Copyright (c) Jupyter Development Team.
5+
# Distributed under the terms of the Modified BSD License.
6+
#
7+
import os
8+
import logging
9+
10+
import docker
11+
12+
import pytest
13+
14+
import requests
15+
16+
from requests.packages.urllib3.util.retry import Retry
17+
from requests.adapters import HTTPAdapter
18+
19+
from typing import Container
20+
21+
22+
LOGGER = logging.getLogger(__name__)
23+
24+
25+
@pytest.fixture(scope="session")
26+
def http_client() -> requests.Session:
27+
"""Requests session with retries and backoff."""
28+
s = requests.Session()
29+
retries = Retry(total=5, backoff_factor=1)
30+
s.mount("http://", HTTPAdapter(max_retries=retries))
31+
s.mount("https://", HTTPAdapter(max_retries=retries))
32+
return s
33+
34+
35+
@pytest.fixture(scope="session")
36+
def docker_client() -> docker.client:
37+
"""Docker client configured based on the host environment"""
38+
return docker.from_env()
39+
40+
41+
@pytest.fixture(scope="session")
42+
def image_name() -> str:
43+
"""Image name to test"""
44+
return os.getenv("TEST_IMAGE", "illumidesk/base-notebook")
45+
46+
47+
class TrackedContainer:
48+
"""Wrapper that collects docker container configuration and delays
49+
container creation/execution.
50+
51+
Parameters
52+
----------
53+
docker_client: docker.DockerClient
54+
Docker client instance
55+
image_name: str
56+
Name of the docker image to launch
57+
**kwargs: dict, optional
58+
Default keyword arguments to pass to docker.DockerClient.containers.run
59+
"""
60+
61+
def __init__(
62+
self, docker_client: docker.client, image_name: str, **kwargs: str
63+
) -> None:
64+
self.container = None
65+
self.docker_client = docker_client
66+
self.image_name = image_name
67+
self.kwargs = kwargs
68+
69+
def run(self, **kwargs: str) -> Container:
70+
"""Runs a docker container using the preconfigured image name
71+
and a mix of the preconfigured container options and those passed
72+
to this method.
73+
74+
Keeps track of the docker.Container instance spawned to kill it
75+
later.
76+
77+
Parameters
78+
----------
79+
**kwargs: dict, optional
80+
Keyword arguments to pass to docker.DockerClient.containers.run
81+
extending and/or overriding key/value pairs passed to the constructor
82+
83+
Returns
84+
-------
85+
docker.Container
86+
"""
87+
all_kwargs = {}
88+
all_kwargs.update(self.kwargs)
89+
all_kwargs.update(kwargs)
90+
LOGGER.info(f"Running {self.image_name} with args {all_kwargs} ...")
91+
self.container = self.docker_client.containers.run(
92+
self.image_name, **all_kwargs
93+
)
94+
return self.container
95+
96+
def remove(self) -> None:
97+
"""Kills and removes the tracked docker container."""
98+
if self.container:
99+
self.container.remove(force=True)
100+
101+
102+
@pytest.fixture(scope="function")
103+
def container(docker_client, image_name) -> None:
104+
"""Notebook container with initial configuration appropriate for testing
105+
(e.g., HTTP port exposed to the host for HTTP calls).
106+
107+
Yields the container instance and kills it when the caller is done with it.
108+
"""
109+
container = TrackedContainer(
110+
docker_client, image_name, detach=True, ports={"8888/tcp": 8888}
111+
)
112+
yield container

0 commit comments

Comments
 (0)