Skip to content

Commit b46ddc6

Browse files
committed
Initial commit
0 parents  commit b46ddc6

19 files changed

+523
-0
lines changed

.dockerignore

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Git
2+
.git
3+
.gitignore
4+
.gitmodules
5+
**/*.md
6+
7+
# Docker
8+
docker-compose.yml
9+
docker-compose.yml.in
10+
.docker
11+
12+
# Python
13+
*.pyc
14+
__pycache__
15+
16+
# Files not used within docker
17+
./tests/*
18+
Makefile
19+
.venv
20+
lib-makefiles/*
21+
buildspec.yml

.gitignore

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
notification-configuration.json
2+
packaged.yaml
3+
4+
# Byte-compiled / optimized / DLL files
5+
__pycache__/
6+
*.py[cod]
7+
*$py.class
8+
9+
# C extensions
10+
*.so
11+
12+
# Distribution / packaging
13+
.Python
14+
env/
15+
_env
16+
build/
17+
develop-eggs/
18+
dist/
19+
downloads/
20+
eggs/
21+
.eggs/
22+
lib/
23+
lib64/
24+
parts/
25+
sdist/
26+
var/
27+
wheels/
28+
*.egg-info/
29+
.installed.cfg
30+
*.egg
31+
local/
32+
33+
# virtualenv
34+
.venv
35+
36+
# mypy
37+
.mypy_cache/
38+
39+
# PyCharm generated files
40+
*.idea
41+
*/bin/
42+
*/lib64
43+
*/pip-selfcheck.json
44+
*/pyvenv.cfg
45+
46+
#vim
47+
*.swp

.style.yapf

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
[style]
2+
based_on_style=google
3+
# Put closing brackets on a separate line, dedented, if the bracketed
4+
# expression can't fit in a single line. Applies to all kinds of brackets,
5+
# including function definitions and calls. For example:
6+
#
7+
# config = {
8+
# 'key1': 'value1',
9+
# 'key2': 'value2',
10+
# } # <--- this bracket is dedented and on a separate line
11+
#
12+
# time_series = self.remote_client.query_entity_counters(
13+
# entity='dev3246.region1',
14+
# key='dns.query_latency_tcp',
15+
# transform=Transformation.AVERAGE(window=timedelta(seconds=60)),
16+
# start_ts=now()-timedelta(days=3),
17+
# end_ts=now(),
18+
# ) # <--- this bracket is dedented and on a separate line
19+
dedent_closing_brackets=True
20+
21+
# Split before arguments, but do not split all subexpressions recursively
22+
# (unless needed).
23+
split_all_top_level_comma_separated_values=True

Dockerfile

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Buster slim python 3.7 base image.
2+
FROM python:3.7-slim-buster
3+
ENV HTTP_PORT 8080
4+
RUN groupadd -r geoadmin && useradd -r -s /bin/false -g geoadmin geoadmin
5+
6+
7+
# HERE : install relevant packages
8+
# RUN apt-get update && apt-get install -y [packages] \
9+
# && apt-get clean \
10+
# && rm -rf /var/lib/apt/lists/*
11+
12+
WORKDIR /app
13+
COPY "./requirements.txt" "/app/requirements.txt"
14+
15+
RUN pip3 install -r requirements.txt
16+
17+
COPY "./" "/app/"
18+
19+
RUN chown -R geoadmin:geoadmin /app
20+
USER geoadmin
21+
22+
EXPOSE $HTTP_PORT
23+
24+
# Use a real WSGI server
25+
ENTRYPOINT ["python3", "wsgi.py"]

Makefile

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
SHELL = /bin/bash
2+
3+
.DEFAULT_GOAL := help
4+
5+
CURRENT_DIR := $(shell pwd)
6+
INSTALL_DIR := $(CURRENT_DIR)/.venv
7+
PYTHON_FILES := $(shell find ./* -type f -name "*.py" -print)
8+
9+
#FIXME: put this variable in config file
10+
PYTHON_VERSION=3.7.4
11+
CURRENT_PYTHON_VERSION := $(shell python3 -c "import sys;t='{v[0]}.{v[1]}.{v[2]}'.format(v=list(sys.version_info[:]));sys.stdout.write(t)")
12+
PYTHON_VERSION_OK := $(shell python3 -c "import sys; t=sys.version_info[0:3]; print(int('{}.{}.{}'.format(*t) == '$(PYTHON_VERSION)'))")
13+
14+
# Commands
15+
SYSTEM_PYTHON_CMD := $(shell which python3)
16+
PYTHON_CMD := $(INSTALL_DIR)/bin/python3
17+
PIP_CMD := $(INSTALL_DIR)/bin/pip3
18+
FLASK_CMD := $(INSTALL_DIR)/bin/flask
19+
YAPF_CMD := $(INSTALL_DIR)/bin/yapf
20+
all: help
21+
22+
# This bit check define the build/python "target": if the system has an acceptable version of python, there will be no need to install python locally.
23+
24+
ifeq ($(PYTHON_VERSION_OK),1)
25+
PYTHON_BINDIR := $(shell dirname $(PYTHON_CMD))
26+
PYTHONHOME := $(shell eval "cd $(PYTHON_BINDIR); pwd; cd > /dev/null")
27+
build/python:
28+
@echo $(CURRENT_PYTHON_VERSION)
29+
@echo $(shell $(PYTHON_CMD) -c "print('OK')")
30+
mkdir -p build
31+
touch build/python;
32+
else
33+
build/python: local/bin/python3.7
34+
mkdir -p build
35+
touch build/python;
36+
37+
SYSTEM_PYTHON_CMD := $(CURRENT_DIR)/local/bin/python3.7
38+
endif
39+
40+
41+
42+
.PHONY: help
43+
help:
44+
@echo "Usage: make <target>"
45+
@echo
46+
@echo "Possible targets:"
47+
@echo -e " \033[1mBUILD TARGETS\033[0m "
48+
@echo "- setup Create the python virtual environment"
49+
@echo -e " \033[1mLINTING TOOLS TARGETS\033[0m "
50+
@echo "- lint Lint the python source code"
51+
@echo -e " \033[1mLOCAL SERVER TARGETS\033[0m "
52+
@echo "- serve Run the project using the flask debug server"
53+
@echo "- gunicornserve Run the project using the gunicorn WSGI server"
54+
@echo "- dockerrun Run the project using the gunicorn WSGI server inside a container. Env variable HTTP_PORT should be set"
55+
@echo "- shutdown Stop the aforementioned container"
56+
@echo -e " \033[1mCLEANING TARGETS\033[0m "
57+
@echo "- clean Clean genereated files"
58+
@echo "- clean_venv Clean python venv"
59+
60+
# Build targets. Calling setup is all that is needed for the local files to be installed as needed. Bundesnetz may cause problem.
61+
62+
python: build/python
63+
@echo "Python installed"
64+
65+
.PHONY: setup
66+
setup: python .venv/build.timestamp
67+
68+
69+
local/bin/python3.7:
70+
mkdir -p $(CURRENT_DIR)/local;
71+
curl -z $(CURRENT_DIR)/local/Python-$(PYTHON_VERSION).tar.xz https://www.python.org/ftp/python/$(PYTHON_VERSION)/Python-$(PYTHON_VERSION).tar.xz -o $(CURRENT_DIR)/local/Python-$(PYTHON_VERSION).tar.xz;
72+
cd $(CURRENT_DIR)/local && tar -xf Python-$(PYTHON_VERSION).tar.xz && Python-$(PYTHON_VERSION)/configure --prefix=$(CURRENT_DIR)/local/ && make altinstall
73+
74+
.venv/build.timestamp: build/python
75+
$(SYSTEM_PYTHON_CMD) -m venv $(INSTALL_DIR) && $(PIP_CMD) install --upgrade pip setuptools
76+
${PIP_CMD} install -r dev_requirements.txt
77+
$(PIP_CMD) install -r requirements.txt
78+
touch .venv/build.timestamp
79+
80+
# linting target, calls upon yapf to make sure your code is easier to read and respects some conventions.
81+
82+
.PHONY: lint
83+
lint: .venv/build.timestamp
84+
$(YAPF_CMD) -i --style .style.yapf $(PYTHON_FILES)
85+
86+
# Serve targets. Using these will run the application on your local machine. You can either serve with a wsgi front (like it would be within the container), or without.
87+
.PHONY: serve
88+
serve: .venv/build.timestamp
89+
FLASK_APP=service_launcher FLASK_DEBUG=1 ${FLASK_CMD} run --host=0.0.0.0 --port=${HTTP_PORT}
90+
91+
.PHONY: gunicornserve
92+
gunicornserve: .venv/build.timestamp
93+
${SYSTEM_PYTHON_CMD} wsgi.py
94+
95+
96+
# Docker related functions.
97+
98+
.PHONY: dockerrun
99+
dockerrun:
100+
docker-compose up -d;
101+
sleep 10
102+
103+
.PHONY: shutdown
104+
shutdown:
105+
docker-compose down
106+
107+
# Cleaning functions. clean_venv will only remove the virtual environment, while clean will also remove the local python installation.
108+
109+
.PHONY: clean
110+
clean: clean_venv
111+
rm -rf local;
112+
113+
.PHONY: clean_venv
114+
clean_venv:
115+
rm -rf ${INSTALL_DIR};

README.md

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
# service-name
2+
3+
## Summary of the project
4+
A simple description of the service should go here
5+
6+
## How to run locally
7+
8+
### dependencies
9+
10+
The **Make** targets assume you have **bash**, **curl**, **tar**, **docker** and **docker-compose** installed.
11+
12+
### Setting up to work
13+
14+
First, you'll need to clone the repo
15+
16+
git clone git@github.com:geoadmin/service-name
17+
18+
Then, you can run the setup target to ensure you have everything needed to develop, test and serve locally
19+
20+
make setup
21+
22+
That's it, you're ready to work.
23+
24+
### Test your work
25+
26+
Testing if what you developed work is made simple. You have four targets at your disposal. **test, serve, gunicornserve, dockerrun**
27+
28+
make test
29+
30+
This command run the integration and unit tests.
31+
32+
make serve
33+
34+
This will serve the application through Flask without any wsgi in front.
35+
36+
make gunicornserve
37+
38+
This will serve the application with the Gunicorn layer in front of the application
39+
40+
make dockerrun
41+
42+
This will serve the application with the wsgi server, inside a container.
43+
To stop serving through containers,
44+
45+
make shutdown
46+
47+
Is the command you're looking for.
48+
49+
## Endpoints
50+
all trailing slashes are optionals
51+
52+
53+
### /checker/ [GET]
54+
55+
#### description of the route
56+
this is a simple route meant to test if the server is up.
57+
#### parameters ####
58+
59+
None
60+
61+
#### expected results
62+
63+
**Success**
64+
65+
"OK", 200
66+
67+
68+
## Deploying the project and continuous integration
69+
When creating a PR, terraform should run a codebuild job to test, build and push automatically your PR as a tagged container.
70+
71+
This service is to be delployed to the Kubernetes cluster once it is merged.
72+
73+
TO DO: give instructions to deploy to kubernetes.

app/__init__.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
from flask import Flask
2+
from app.middleware import ReverseProxied
3+
4+
# Standard Flask application initialisation
5+
6+
app = Flask(__name__)
7+
app.wsgi_app = ReverseProxied(app.wsgi_app, script_name='/')
8+
9+
10+
from app import routes
11+
12+
13+
def main():
14+
app.run()
15+
16+
17+
if __name__ == '__main__':
18+
"""
19+
Entrypoint for the application. At the moment, we do nothing specific, but there might be preparatory steps in the
20+
future
21+
"""
22+
main()

app/helpers/__init__.py

Whitespace-only changes.

app/middleware.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
class ReverseProxied(object):
2+
"""
3+
Reverse proxies can cause some problems within applications, as they change routes, redirect traffic, and
4+
applications might have errors because of that. This piece of middlewar make sure everything runs smoothly.
5+
"""
6+
def __init__(self, app, script_name=None, scheme=None, server=None, port=None):
7+
self.app = app
8+
self.script_name = script_name
9+
self.scheme = scheme
10+
self.server = server
11+
self.port = port
12+
13+
def __call__(self, environ, start_response):
14+
"""
15+
This function modifies the environment received by the WSGI, mostly making sure we serve the right routes and
16+
that the application answers as if it were the initial host (for example, an error should say that the site at
17+
reverse-proxied.admin.ch/generic_name/generate encountered a problem, not that it was at internal.server/generate
18+
:param environ: the WSGI environment
19+
:param start_response: A callable accepting headers, a status code and can accept an exception to start the
20+
response
21+
:return: a call to the wsgi app, with an updated WSGI environment.
22+
"""
23+
24+
"""
25+
The first part makes sure the route goes to the right resource.
26+
If we have a query made to reverse-proxy.admin.ch/generic_name/generate,
27+
HTTP_X_SCRIPT_NAME will be /generic_name, and PATH_INFO would be /generic_name/generate.
28+
29+
PATH_INFO is used to handle the route called (in this case, it would use /generate)
30+
SCRIPT_NAME is used to prefix the routes so that the application returns a correct answer (namely: that the
31+
query was hitting /generic_name/generate).
32+
"""
33+
# The syntax here means : try to get HTTP_X_SCRIPT_NAME, or get me an empty string, and the command or, in the
34+
# context of a string, returns the first "truthy" value
35+
script_name = environ.get('HTTP_X_SCRIPT_NAME', '') or self.script_name
36+
if script_name:
37+
environ['SCRIPT_NAME'] = script_name
38+
path_info = environ['PATH_INFO']
39+
if path_info.startswith(script_name):
40+
environ['PATH_INFO'] = path_info[len(script_name):]
41+
# The scheme is the protocol used (http/s) to connect to the server.
42+
scheme = environ.get('HTTP_X_SCHEME', '') or self.scheme
43+
if scheme:
44+
environ['wsgi.url_scheme'] = scheme
45+
# this sets our own HOST parameter to be the one that was queried in the first place.
46+
server = environ.get('HTTP_X_FORWARDED_HOST', '') or self.server
47+
if server:
48+
environ['HTTP_HOST'] = server
49+
return self.app(environ, start_response)

app/routes.py

Whitespace-only changes.

0 commit comments

Comments
 (0)