ARTICLES
Simplifiez votre développement et votre CI avec des Makefile
By Valentin Bremond
Old, but gold
Je sais ce que vous pensez. On a tous découvert ça à travers un gros Makefile dégeulasse d’un vieux projet pourri en C qui vous a fait douter de votre choix de bosser dans l’info dès lors que vous l’avez ouvert.
Oui, les Makefile ça peut vite devenir dégeulasse. Mais c’est également très pratique pour :
- bosser à plusieurs sur un projet (pas la peine que tout le monde se rappelle de comment lancer le back puis le front, ou comment builder le bordel)
- avoir un worflow développeur de build identique au workflow de build de la CI (on évite la redondance entre le Jenkinsfile et les scripts branlants des divers devs du projet)
- avoir un workflow quasi identique entre les différents projets (
make build, ,make runetmake testsseront, à priori, toujours applicables, peu importe le projet et surtout peu importe le language) - profiter de toute la puissance de
make(sa parallélisation avec-j, sa gestion des dépendances entre les tasks) et de sa maturité et de son ubiquité (GNU Make est arrivé en 1976. Citez-moi un OS qui n’a pasmakedéjà installé. Même MacOS l’a, c’est dire !)
Maintenant couplez ça avec Docker : en une commande, vous buildez/testez tout votre projet sans avoir besoin de rien (bon, sauf Docker, OK). C’est votre manager qui sera content (pouvoir tester soi-même, à tout moment, sans rien avoir à installer et valider qu’on est toujours à 100 % de coverage, ça n’a pas de prix pour lui).
Exemple concret
Mettons que vous soyiez en train de développer une petite API d’un site de type blog en Python avec Flask et que vous vouliez la “Pexer” pour la déployer facilement.
Mettons que votre application ressemble à peu près à ça :
1api/
2 __init__.py
3 __main__.py
4 models/
5 __init__.py
6 article.py
7 user.py
8 routes/
9 __init__.py
10 article.py
11 user.py
12docker/
13 Dockerfile
14 run_as.sh
15Makefile
16requirements/
17 build.txt
18 run.txt
19 tests.txt
20setup.py
21tests/
22 conftest.py
23 test_add_user.pyVoyons à quoi pourrait ressembler votre Makefile :
1.PHONY: requirements image help
2.PHONY: build run tests clean
3.PHONY: _run _tests
4
5.DEFAULT_GOAL: help
6
7# Make sure the default shell is Bash (I'm looking at you Ubuntu!)
8SHELL := /bin/bash
9
10PWD := $(shell pwd)
11
12DOCKER_IMAGE := api:makefile
13
14# This "function" will allow us to call Docker easily in the targets below
15define docker_make
16 sudo docker run -it --rm -e USER_ID=$(shell id -u) -v ${PWD}:${PWD}:Z --network=host ${DOCKER_IMAGE} "cd ${PWD} && make $1"
17endef
18
19PYTHON_FILES = $(shell find api/ -name '*.py')
20
21
22define HELP_CONTENT
23make build Build the app (create a Pex out of it).
24make clean Clean built software and downloaded libraries.
25make help Display this help.
26make run Run the app (for development purposes).
27make tests Test the app.
28endef
29export HELP_CONTENT
30
31help:
32 @echo "$$HELP_CONTENT"
33
34
35clean:
36 $(RM) -r venv/
37 $(RM) -r api.egg-info/
38 $(RM) -r .pytest_cache/
39 find . -type f -name '*.pyc' -delete
40 find . -type d -name '__pycache__' -delete
41 sudo docker images | grep -E 'api\s+makefile' && sudo docker rmi ${DOCKER_IMAGE} || true
42
43
44
45
46# Check that Docker is installed
47requirements:
48 @command -v docker &>/dev/null || { echo 'You need docker, please install it first'; exit 1; }
49
50# Build the Docker image
51image: requirements
52 cd docker/ && sudo docker build . -t ${DOCKER_IMAGE}
53
54build: image
55 $(call docker_make,api.pex)
56
57run: image
58 $(call docker_make,_run)
59
60tests: image
61 $(call docker_make,_tests)
62
63
64# The following targets will be run in the Docker container
65
66# Only build the virtualenv when needed
67venv/:
68 virtualenv-3.4 $@
69
70api.pex: ${PYTHON_FILES} requirements/run.txt requirements/build.txt venv/
71 venv/bin/pip3.4 install -r requirements/run.txt -r requirements/build.txt
72 venv/bin/pex . -r requirements/run.txt --script api --output $@
73
74_run: venv/
75 venv/bin/pip3.4 install -r requirements/run.txt
76 venv/bin/python3.4 api/
77
78_tests: venv/
79 venv/bin/pip3.4 install -r requirements/run.txt -r requirements/tests.txt
80 venv/bin/pytest tests/
Pour tester votre projet et en faire un Pex si tout est bon : make tests build. Simple n’est-ce pas ?
Vous noterez la présence d’un fichier run_as.sh dans ce projet. Son but est de permettre au container de toujours avoir l’UID de l’utilisateur.
Si vous ne faites pas attention à ça, vous allez vour retrouver avec des fichiers appartenant à root (peu pratique, vous en conviendrez).
Voici le contenu du docker/Dockerfile (assez simple) :
1FROM centos
2
3RUN yum install -y epel-release
4
5RUN yum install -y \
6 make \
7 python34 python34-pip python34-virtualenv \
8 sudo
9
10COPY run_as.sh /usr/bin/run_as
11RUN chmod 755 /usr/bin/run_as
12
13ENTRYPOINT ["/usr/bin/run_as"]
14
15CMD "echo 'You have to provide the command to run' >&2 && exit 1"ainsi que (plus important) celui du docker/run_as.sh :
1#!/usr/bin/env bash
2
3if ! id -u "$USER_ID" &>/dev/null; then
4 groupadd -g "$USER_ID" "$USER_ID"
5 useradd -u "$USER_ID" -g "$USER_ID" "$USER_ID"
6fi
7
8exec sudo -u "$USER_ID" -- bash -c "$@"Conclusion
Vous voyez, c’est pas si compliqué mais c’est super pratique ! Point bonus pour votre CI : vous n’avez plus qu’à faire des make build dans votre Jenkins,
le reste est géré par make (vous vous retrouvez avec le même workflow pour les développeurs et votre CI, et ça, c’est le bien).
Vous pouvez retrouver l’exemple que j’ai décrit ici sur ce repo (c’est plus facile pour tester).