Skip to content

Commit b2444ea

Browse files
authored
Tidy up, fix warnings and use a Makefile (#508)
* Tidy up, fix warnings and use a Makefile * Update README.md * Remove make dependency in docker image * Remove unused USE_VENV variable * Can't run specific tests with docker-compose * Fix pagination with no items
1 parent 936fcc5 commit b2444ea

14 files changed

+134
-100
lines changed

Dockerfile

+25-46
Original file line numberDiff line numberDiff line change
@@ -2,63 +2,42 @@ FROM alpine:3.7
22

33
MAINTAINER Andy Driver <andy.driver@digital.justice.gov.uk>
44

5-
# install build dependencies (they'll be uninstalled after pip install)
6-
RUN apk add --no-cache \
7-
--virtual build-deps \
8-
gcc \
9-
musl-dev
5+
ENV HELM_VERSION 2.9.1
6+
ENV HELM_HOME /tmp/helm
7+
ENV DJANGO_SETTINGS_MODULE "control_panel_api.settings"
108

11-
# install python3 and 'ca-certificates' so that HTTPS works consistently
9+
WORKDIR /home/control-panel
10+
11+
# install build dependencies (they'll be uninstalled after pip install)
1212
RUN apk add --no-cache \
13+
build-base \
1314
openssl \
1415
ca-certificates \
1516
libffi-dev \
16-
python3-dev
17-
18-
# Temporary bugfix for libressl
19-
# Postgres needs libressl-dev, but cryptography only works with openssl-dev
20-
RUN apk add --no-cache --virtual temp-ssl-fix \
21-
openssl-dev \
22-
&& pip3 install cryptography==2.2.2 \
23-
&& apk del temp-ssl-fix \
24-
&& apk add --no-cache \
17+
python3-dev \
2518
libressl-dev \
2619
postgresql-dev
2720

28-
# Install helm
29-
ENV HELM_VERSION 2.9.1
30-
RUN wget https://storage.googleapis.com/kubernetes-helm/helm-v$HELM_VERSION-linux-amd64.tar.gz \
31-
&& tar xzf helm-v$HELM_VERSION-linux-amd64.tar.gz \
32-
&& mv linux-amd64/helm /usr/local/bin \
33-
&& rm -rf helm-v$HELM_VERSION-linux-amd64.tar.gz linux-amd64
34-
35-
# Configure helm
36-
ENV HELM_HOME /tmp/helm
37-
RUN helm init --client-only
21+
# Install and configure helm
3822
COPY helm-repositories.yaml /tmp/helm/repository/repositories.yaml
39-
RUN helm repo update
40-
41-
WORKDIR /home/control-panel
42-
43-
# install python dependencies
44-
ADD requirements.txt requirements.txt
45-
RUN pip3 install -r requirements.txt
46-
47-
# uninstall build dependencies
48-
RUN apk del build-deps
49-
50-
ENV DJANGO_SETTINGS_MODULE "control_panel_api.settings"
51-
52-
ADD manage.py manage.py
53-
ADD run_api run_api
54-
ADD run_tests run_tests
55-
ADD wait_for_db wait_for_db
56-
ADD control_panel_api control_panel_api
57-
ADD moj_analytics moj_analytics
23+
RUN wget https://storage.googleapis.com/kubernetes-helm/helm-v${HELM_VERSION}-linux-amd64.tar.gz -O helm.tgz \
24+
&& tar fxz helm.tgz \
25+
&& mv linux-amd64/helm /usr/local/bin \
26+
&& rm -rf helm.tgz linux-amd64 \
27+
&& helm init --client-only \
28+
&& helm repo update
29+
30+
# install python dependencies (and then remove build dependencies)
31+
COPY requirements.txt ./
32+
RUN pip3 install -r requirements.txt \
33+
&& apk del build-base
34+
35+
COPY control_panel_api control_panel_api
36+
COPY moj_analytics moj_analytics
37+
COPY manage.py wait_for_db ./
5838

5939
# collect static files for deployment
6040
RUN python3 manage.py collectstatic
6141

6242
EXPOSE 8000
63-
64-
CMD ["./run_api"]
43+
CMD ["gunicorn", "-b", "0.0.0.0:8000", "control_panel_api.wsgi:application"]

Makefile

+64
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
HOST=0.0.0.0
2+
PORT=8000
3+
PROJECT=control-panel
4+
MODULE=control_panel_api
5+
VENV=venv
6+
BIN=${VENV}/bin
7+
8+
-include .env
9+
export
10+
11+
.PHONY: collectstatic dependencies help run test wait_for_db
12+
13+
venv/bin:
14+
@if ${USE_VENV} && [ ! -d "${VENV}" ] ; then python3 -m venv ${VENV} ; fi
15+
16+
## dependencies: Install dependencies
17+
dependencies: ${BIN} requirements.txt
18+
@echo
19+
@echo "> Fetching dependencies..."
20+
@${BIN}/pip3 install -r requirements.txt
21+
22+
## collectstatic: Collect assets into static folder
23+
collectstatic: dependencies
24+
@echo
25+
@echo "> Collecting static assets..."
26+
@${BIN}/python3 manage.py collectstatic --noinput
27+
28+
## run: Run webapp
29+
run: collectstatic
30+
@echo
31+
@echo "> Running webapp..."
32+
@${BIN}/gunicorn -b ${HOST}:${PORT} ${MODULE}.wsgi:application
33+
34+
wait_for_db:
35+
@echo
36+
@echo "> Waiting for database..."
37+
@${BIN}/python3 wait_for_db
38+
39+
## test: Run tests
40+
test: export DJANGO_SETTINGS_MODULE=${MODULE}.settings.test
41+
test: wait_for_db
42+
@echo
43+
@echo "> Running tests..."
44+
@NAMED_TESTS="$(shell if [ -n "${TEST_NAME}" ]; then echo "-k ${TEST_NAME}" ; fi)" && \
45+
${BIN}/pytest --color=yes ${MODULE} $$NAMED_TESTS
46+
47+
## docker-image: Build docker image
48+
docker-image:
49+
@echo
50+
@echo "> Building docker image..."
51+
@docker build -t ${PROJECT} .
52+
53+
## docker-test: Run tests in Docker container
54+
docker-test:
55+
@echo
56+
@echo "> Running tests in Docker..."
57+
@docker-compose -f docker-compose.test.yml up --abort-on-container-exit
58+
59+
help: Makefile
60+
@echo
61+
@echo " Commands in "$(PROJECT)":"
62+
@echo
63+
@sed -n 's/^##//p' $< | column -t -s ':' | sed -e 's/^/ /'
64+
@echo

README.md

+23-18
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ https://github.com/ministryofjustice/analytics-platform-control-panel-frontend
99
## Running with Docker
1010

1111
```sh
12-
docker-compose build
12+
docker-compose build # OR make docker-image
1313
docker-compose up
1414
```
1515
and then in a separate terminal window,
@@ -21,12 +21,7 @@ Then browse to http://localhost:8000/
2121
### Running tests with docker
2222

2323
```sh
24-
docker-compose -f docker-compose.test.yml up --build --abort-on-container-exit
25-
```
26-
27-
You can run a particular test using the pytest '-k' parameter:
28-
```sh
29-
docker-compose -f docker-compose.test.yml build && docker-compose -f docker-compose.test.yml run app ./run_tests -k TEST-NAME
24+
make docker-test
3025
```
3126

3227
## Running directly on your machine
@@ -35,11 +30,11 @@ docker-compose -f docker-compose.test.yml build && docker-compose -f docker-comp
3530

3631
The Control Panel app requires Python 3.6+
3732

38-
It is best to use a virtual environment to install python dependencies, eg:
33+
Install dependencies with the following command:
3934
```sh
40-
python -m venv venv
41-
. venv/bin/activate
42-
pip install -r requirements.txt
35+
python3 -m venv venv
36+
source venv/bin/activate
37+
pip3 install -r requirements.txt
4338
```
4439

4540
### Kubernetes setup
@@ -90,40 +85,50 @@ export DJANGO_SETTINGS_MODULE=control_panel_api.settings
9085

9186
The Control Panel app connects to a PostgreSQL database, which should have a database with the expected name:
9287
```sh
93-
createdb $DB_NAME
88+
createuser -d controlpanel
89+
createdb -U controlpanel controlpanel
9490
```
9591

9692
Then you can run migrations:
9793
```sh
98-
python manage.py migrate
94+
python3 manage.py migrate
9995
```
10096

10197
### Create superuser (on first run only)
10298

10399
```sh
104-
python manage.py createsuperuser
100+
python3 manage.py createsuperuser
105101
```
106102
NB `Username` needs to be your GitHub username
107103

108104
### Compile Sass and Javascript
109105

110106
Before the first run (or after changes to static assets), you need to run
111107
```sh
112-
python manage.py collectstatic
108+
python3 manage.py collectstatic
113109
```
114110

115111
### Run the app
116112

117-
You can run the app with
113+
You can run the app with the Django development server with
114+
```sh
115+
python3 manage.py runserver
116+
```
117+
Or with Gunicorn WSGI server:
118118
```sh
119-
./run_api
119+
gunicorn -b 0.0.0.0:8000 control_panel_api.wsgi:application
120120
```
121121
Go to http://localhost:8000/
122122

123123
### How to run the tests
124124

125125
```sh
126-
./run_tests
126+
make test
127+
```
128+
129+
You can run a specific test class or function by passing the `TEST_NAME` parameter, eg:
130+
```sh
131+
make test TEST_NAME=test_something
127132
```
128133

129134
# Deployment

control_panel_api/k8s_patch.py

+5-10
Original file line numberDiff line numberDiff line change
@@ -7,24 +7,19 @@
77
from kubernetes.config.kube_config import _is_expired
88

99

10-
def load_token(self):
11-
if 'auth-provider' not in self._user:
12-
return
13-
14-
provider = self._user['auth-provider']
15-
16-
if ('name' not in provider
17-
or 'config' not in provider
18-
or provider['name'] != 'oidc'):
10+
def load_token(self, provider):
11+
if 'config' not in provider:
1912
return
2013

2114
parts = provider['config']['id-token'].split('.')
2215

2316
if len(parts) != 3: # Not a valid JWT
2417
return None
2518

19+
padding = (4 - len(parts[1]) % 4) * '='
20+
2621
jwt_attributes = json.loads(
27-
base64.b64decode(parts[1] + '==').decode('utf-8')
22+
base64.b64decode(parts[1] + padding).decode('utf-8')
2823
)
2924

3025
expire = jwt_attributes.get('exp')

control_panel_api/pagination.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ def paginate_queryset(self, queryset, request, view=None):
1717
if not self._page_size_is_all(page_size):
1818
return super().paginate_queryset(queryset, request, view)
1919

20-
paginator = self.django_paginator_class(queryset, queryset.count())
20+
paginator = self.django_paginator_class(queryset, queryset.count() or 1)
2121
self.page = paginator.page(1)
2222

2323
return list(self.page)

control_panel_api/serializers.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -295,7 +295,7 @@ def to_representation(self, bucket_hits):
295295
return sorted(results, key=itemgetter('count'), reverse=True)
296296

297297
def _get_accessed_by(self, key):
298-
match = re.search(f"{settings.ENV}_(app|user)_([\w-]+)/", key)
298+
match = re.search(rf"{settings.ENV}_(app|user)_([\w-]+)/", key)
299299

300300
if match:
301301
return match.group(1), match.group(2)

control_panel_api/settings/base.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -116,9 +116,9 @@ def is_enabled(value):
116116
# Static files (CSS, JavaScript, Images)
117117
# https://docs.djangoproject.com/en/1.11/howto/static-files/
118118

119-
STATIC_ROOT = os.path.join(BASE_DIR, 'static/')
120-
121-
STATIC_URL = '/static/'
119+
STATIC_ROOT = os.path.join(os.path.dirname(BASE_DIR), 'static')
120+
STATIC_HOST = os.environ.get('DJANGO_STATIC_HOST', '')
121+
STATIC_URL = STATIC_HOST + '/static/'
122122

123123
REST_FRAMEWORK = {
124124
'DEFAULT_AUTHENTICATION_CLASSES': [

control_panel_api/settings/test.py

+4
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,7 @@
2121
}
2222

2323
LOGGING['handlers']['console']['level'] = 'CRITICAL'
24+
ENABLED = {
25+
'k8s_rbac': False,
26+
'write_to_cluster': True,
27+
}

control_panel_api/views.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ class AppViewSet(viewsets.ModelViewSet):
123123
filter_backends = (DjangoFilterBackend,)
124124
permission_classes = (AppPermissions,)
125125

126-
filter_fields = ('name', 'repo_url', 'slug')
126+
filterset_fields = ('name', 'repo_url', 'slug')
127127

128128
@handle_external_exceptions
129129
@transaction.atomic
@@ -238,7 +238,7 @@ class S3BucketViewSet(viewsets.ModelViewSet):
238238
serializer_class = S3BucketSerializer
239239
filter_backends = (S3BucketFilter,)
240240
permission_classes = (S3BucketPermissions,)
241-
filter_fields = ('is_data_warehouse',)
241+
filterset_fields = ('is_data_warehouse',)
242242

243243
@handle_external_exceptions
244244
@transaction.atomic

docker-compose.test.yml

+2-2
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ version: '3'
22
services:
33
app:
44
build: .
5-
image: "app"
5+
image: "control-panel"
66
ports:
77
- "8000:8000"
88
depends_on:
@@ -15,7 +15,7 @@ services:
1515
DB_USER: "postgres"
1616
DEBUG: "True"
1717
DJANGO_SETTINGS_MODULE: "control_panel_api.settings.test"
18-
command: ["./run_tests"]
18+
command: sh -c "python3 wait_for_db && pytest --color=yes control_panel_api"
1919
db:
2020
image: "postgres:9.6.2"
2121
logging:

docker-compose.yml

+3-3
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ version: '3'
22
services:
33
app:
44
build: .
5-
image: app
5+
image: control-panel
66
ports:
77
- "8000:8000"
88
depends_on:
@@ -22,8 +22,8 @@ services:
2222
POSTGRES_USER: "controlpanel"
2323
POSTGRES_DB: "controlpanel"
2424
migration:
25-
image: app:latest
26-
command: ["sh", "-c", "python3 wait_for_db && python3 manage.py migrate"]
25+
image: control-panel
26+
command: sh -c "python3 wait_for_db && python3 manage.py migrate"
2727
environment:
2828
DB_HOST: "db"
2929
DB_NAME: "controlpanel"

requirements.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ kubernetes==8.0.1
3030
MarkupSafe==1.1.0
3131
model-mommy==1.6.0
3232
openapi-codec==1.3.2
33-
psycopg2==2.7.7
33+
psycopg2-binary==2.7.7
3434
py==1.7.0
3535
pyasn1==0.4.4
3636
pyasn1-modules==0.2.2

run_api

-6
This file was deleted.

run_tests

-7
This file was deleted.

0 commit comments

Comments
 (0)