diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..a295864 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,2 @@ +*.pyc +__pycache__ diff --git a/.envrc.dist b/.envrc.dist index 4863cc7..0bbf608 100644 --- a/.envrc.dist +++ b/.envrc.dist @@ -6,3 +6,7 @@ export OVH_ENDPOINT=ovh-eu export OVH_APPLICATION_KEY= export OVH_APPLICATION_SECRET= export OVH_CONSUMER_KEY= +export PORT=5000 +export REDIS_HOST=localhost +export REDIS_PORT=6379 +export REDIS_DB=0 diff --git a/.gitignore b/.gitignore index 994c37d..c10eba1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ .idea .DS_Store .python-version +*.env # Created by https://www.toptal.com/developers/gitignore/api/osx,pycharm,python # Edit at https://www.toptal.com/developers/gitignore?templates=osx,pycharm,python diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..37e5b92 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,55 @@ +## Build venv +FROM python:3.9.5-buster AS venv + +# https://python-poetry.org/docs/#installation +ENV POETRY_VERSION=1.1.6 +RUN curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py | python + +ENV PATH /root/.poetry/bin:$PATH +ENV PYTHONPATH $PYTHONPATH:/root/.poetry/lib + +WORKDIR /app + +COPY pyproject.toml poetry.lock ./ + +RUN python -m venv --copies /app/venv \ + && . /app/venv/bin/activate \ + && poetry install --no-dev + + +## Get git versions +FROM alpine/git:v2.30.2 AS git +ADD . /app +WORKDIR /app +RUN git rev-parse HEAD | tee /version + + +## Beginning of runtime image +FROM python:3.9.5-slim-buster as prod + +RUN echo "Europe/Paris" > /etc/timezone + +COPY --from=venv /app/venv /app/venv/ +ENV PATH /app/venv/bin:$PATH +ENV PYTHONPATH $PYTHONPATH:/app + +WORKDIR /app +COPY pyproject.toml ./ +COPY shortener ./shortener/ +COPY --from=git /version /app/.version + +ENV FLASK_APP shortener.main +ENV FLASK_ENV production +ENV BASE_DOMAIN g4b.ovh +ENV PROVIDER ovh +ENV OVH_ENDPOINT ovh-eu +ENV OVH_APPLICATION_KEY "" +ENV OVH_APPLICATION_SECRET "" +ENV OVH_CONSUMER_KEY "" +ENV REDIS_HOST redis +ENV REDIS_PORT 6379 +ENV REDIS_DB 0 + +HEALTHCHECK --start-period=30s CMD python -c "import requests; requests.get('http://localhost:5000', timeout=2)" + +CMD ["gunicorn", "--bind", "0.0.0.0:5000", "shortener.wsgi:app"] diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 0000000..efba41e --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,13 @@ +version: "2.4" + +services: + shortener: + image: crocmagnon/shortener + build: . + env_file: shortener.env + ports: + - 5000:5000 + depends_on: + - redis + redis: + image: redis:6 diff --git a/poetry.lock b/poetry.lock index 87c7ac2..68b4a13 100644 --- a/poetry.lock +++ b/poetry.lock @@ -28,6 +28,14 @@ docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"] tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface"] tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins"] +[[package]] +name = "certifi" +version = "2020.12.5" +description = "Python package for providing Mozilla's CA Bundle." +category = "main" +optional = false +python-versions = "*" + [[package]] name = "cfgv" version = "3.3.0" @@ -36,6 +44,14 @@ category = "dev" optional = false python-versions = ">=3.6.1" +[[package]] +name = "chardet" +version = "4.0.0" +description = "Universal encoding detector for Python 2 and 3" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + [[package]] name = "click" version = "8.0.1" @@ -104,6 +120,20 @@ Werkzeug = ">=2.0" async = ["asgiref (>=3.2)"] dotenv = ["python-dotenv"] +[[package]] +name = "gunicorn" +version = "20.1.0" +description = "WSGI HTTP Server for UNIX" +category = "main" +optional = false +python-versions = ">=3.5" + +[package.extras] +eventlet = ["eventlet (>=0.24.1)"] +gevent = ["gevent (>=1.4.0)"] +setproctitle = ["setproctitle"] +tornado = ["tornado (>=0.2)"] + [[package]] name = "identify" version = "2.2.6" @@ -115,6 +145,14 @@ python-versions = ">=3.6.1" [package.extras] license = ["editdistance-s"] +[[package]] +name = "idna" +version = "2.10" +description = "Internationalized Domain Names in Applications (IDNA)" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + [[package]] name = "itsdangerous" version = "2.0.1" @@ -268,6 +306,24 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [package.extras] hiredis = ["hiredis (>=0.1.3)"] +[[package]] +name = "requests" +version = "2.25.1" +description = "Python HTTP for Humans." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[package.dependencies] +certifi = ">=2017.4.17" +chardet = ">=3.0.2,<5" +idna = ">=2.5,<3" +urllib3 = ">=1.21.1,<1.27" + +[package.extras] +security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)"] +socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"] + [[package]] name = "six" version = "1.16.0" @@ -284,6 +340,19 @@ category = "dev" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" +[[package]] +name = "urllib3" +version = "1.26.5" +description = "HTTP library with thread-safe connection pooling, file post, and more." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" + +[package.extras] +brotli = ["brotlipy (>=0.6.0)"] +secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] +socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] + [[package]] name = "virtualenv" version = "20.4.7" @@ -324,7 +393,7 @@ watchdog = ["watchdog"] [metadata] lock-version = "1.1" python-versions = "^3.9" -content-hash = "7bb0d58e52c792760a354cbff8e7ae8b642d5d29fabf3210a1b32d1f5e025a81" +content-hash = "524e8d0aa500e21953ad6bb630504c3455d1dadaf2b310b22b52a233496221a7" [metadata.files] appdirs = [ @@ -339,10 +408,18 @@ attrs = [ {file = "attrs-21.2.0-py2.py3-none-any.whl", hash = "sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1"}, {file = "attrs-21.2.0.tar.gz", hash = "sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb"}, ] +certifi = [ + {file = "certifi-2020.12.5-py2.py3-none-any.whl", hash = "sha256:719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830"}, + {file = "certifi-2020.12.5.tar.gz", hash = "sha256:1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c"}, +] cfgv = [ {file = "cfgv-3.3.0-py2.py3-none-any.whl", hash = "sha256:b449c9c6118fe8cca7fa5e00b9ec60ba08145d281d52164230a69211c5d597a1"}, {file = "cfgv-3.3.0.tar.gz", hash = "sha256:9e600479b3b99e8af981ecdfc80a0296104ee610cab48a5ae4ffd0b668650eb1"}, ] +chardet = [ + {file = "chardet-4.0.0-py2.py3-none-any.whl", hash = "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"}, + {file = "chardet-4.0.0.tar.gz", hash = "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa"}, +] click = [ {file = "click-8.0.1-py3-none-any.whl", hash = "sha256:fba402a4a47334742d782209a7c79bc448911afe1149d07bdabdf480b3e2f4b6"}, {file = "click-8.0.1.tar.gz", hash = "sha256:8c04c11192119b1ef78ea049e0a6f0463e4c48ef00a30160c704337586f3ad7a"}, @@ -367,10 +444,18 @@ flask = [ {file = "Flask-2.0.1-py3-none-any.whl", hash = "sha256:a6209ca15eb63fc9385f38e452704113d679511d9574d09b2cf9183ae7d20dc9"}, {file = "Flask-2.0.1.tar.gz", hash = "sha256:1c4c257b1892aec1398784c63791cbaa43062f1f7aeb555c4da961b20ee68f55"}, ] +gunicorn = [ + {file = "gunicorn-20.1.0-py3-none-any.whl", hash = "sha256:9dcc4547dbb1cb284accfb15ab5667a0e5d1881cc443e0677b4882a4067a807e"}, + {file = "gunicorn-20.1.0.tar.gz", hash = "sha256:e0a968b5ba15f8a328fdfd7ab1fcb5af4470c28aaf7e55df02a99bc13138e6e8"}, +] identify = [ {file = "identify-2.2.6-py2.py3-none-any.whl", hash = "sha256:1560bb645b93d5c05c3535c72a4f4884133006423d02c692ac6862a45eb0d521"}, {file = "identify-2.2.6.tar.gz", hash = "sha256:01ebbc7af37043806216c7550539210cde4f82451983eb8735a02b3b9d013e40"}, ] +idna = [ + {file = "idna-2.10-py2.py3-none-any.whl", hash = "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"}, + {file = "idna-2.10.tar.gz", hash = "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6"}, +] itsdangerous = [ {file = "itsdangerous-2.0.1-py3-none-any.whl", hash = "sha256:5174094b9637652bdb841a3029700391451bd092ba3db90600dea710ba28e97c"}, {file = "itsdangerous-2.0.1.tar.gz", hash = "sha256:9e724d68fc22902a1435351f84c3fb8623f303fffcc566a4cb952df8c572cff0"}, @@ -478,6 +563,10 @@ redis = [ {file = "redis-3.5.3-py2.py3-none-any.whl", hash = "sha256:432b788c4530cfe16d8d943a09d40ca6c16149727e4afe8c2c9d5580c59d9f24"}, {file = "redis-3.5.3.tar.gz", hash = "sha256:0e7e0cfca8660dea8b7d5cd8c4f6c5e29e11f31158c0b0ae91a397f00e5a05a2"}, ] +requests = [ + {file = "requests-2.25.1-py2.py3-none-any.whl", hash = "sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e"}, + {file = "requests-2.25.1.tar.gz", hash = "sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804"}, +] six = [ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, @@ -486,6 +575,10 @@ toml = [ {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, ] +urllib3 = [ + {file = "urllib3-1.26.5-py2.py3-none-any.whl", hash = "sha256:753a0374df26658f99d826cfe40394a686d05985786d946fbe4165b5148f5a7c"}, + {file = "urllib3-1.26.5.tar.gz", hash = "sha256:a7acd0977125325f516bda9735fa7142b909a8d01e8b2e4c8108d0984e6e0098"}, +] virtualenv = [ {file = "virtualenv-20.4.7-py2.py3-none-any.whl", hash = "sha256:2b0126166ea7c9c3661f5b8e06773d28f83322de7a3ff7d06f0aed18c9de6a76"}, {file = "virtualenv-20.4.7.tar.gz", hash = "sha256:14fdf849f80dbb29a4eb6caa9875d476ee2a5cf76a5f5415fa2f1606010ab467"}, diff --git a/pyproject.toml b/pyproject.toml index e74530a..4bb7f3f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,6 +10,8 @@ dnspython = "^2.1.0" Flask = "^2.0.0" ovh = "^0.5.0" redis = "^3.5.3" +requests = "^2.25.1" +gunicorn = "^20.1.0" [tool.poetry.dev-dependencies] pytest = "^5.2" diff --git a/shortener/wsgi.py b/shortener/wsgi.py new file mode 100644 index 0000000..9bfa081 --- /dev/null +++ b/shortener/wsgi.py @@ -0,0 +1,4 @@ +from shortener.main import app + +if __name__ == "__main__": + app.run()