Modernize whole stack
This commit is contained in:
		
							
								
								
									
										6
									
								
								.gitlint
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								.gitlint
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | |||||||
|  | [general] | ||||||
|  | ignore=body-is-missing,T3 | ||||||
|  | ignore-stdin=true | ||||||
|  |  | ||||||
|  | [title-match-regex] | ||||||
|  | regex=[A-Z] | ||||||
							
								
								
									
										11
									
								
								.pre-commit-config.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								.pre-commit-config.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,11 @@ | |||||||
|  | repos: | ||||||
|  | -   repo: https://github.com/PyCQA/flake8 | ||||||
|  |     rev: 3.9.2 | ||||||
|  |     hooks: | ||||||
|  |     -   id: flake8 | ||||||
|  |         additional_dependencies: [flake8-typing-imports==1.10.0,flake8-quotes==3.2.0] | ||||||
|  |  | ||||||
|  | -   repo: https://github.com/jorisroovers/gitlint | ||||||
|  |     rev: v0.15.1 | ||||||
|  |     hooks: | ||||||
|  |     -   id: gitlint | ||||||
| @@ -1,8 +1,6 @@ | |||||||
| FROM python:3 | FROM python:3 | ||||||
| WORKDIR /app | WORKDIR /app | ||||||
|  |  | ||||||
| COPY requirements.txt ./ | COPY requirements.txt ./ | ||||||
| RUN pip install -r requirements.txt | RUN pip install -r requirements.txt | ||||||
|  |  | ||||||
| COPY *.py ./ | COPY *.py ./ | ||||||
| CMD python doorboy.py | CMD python doorboy.py | ||||||
|   | |||||||
							
								
								
									
										20
									
								
								Jenkinsfile
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										20
									
								
								Jenkinsfile
									
									
									
									
										vendored
									
									
								
							| @@ -1,20 +0,0 @@ | |||||||
| #!/usr/bin/groovy |  | ||||||
| image = "doorboy-proxy" |  | ||||||
| label = "latest" |  | ||||||
| build = null |  | ||||||
|  |  | ||||||
| node { |  | ||||||
|     stage("Checkout") { |  | ||||||
|         checkout scm |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     stage("Build") { |  | ||||||
|         build = docker.build("kspaceee/${image}") |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     stage("Push") { |  | ||||||
|         docker.withRegistry("https://registry.hub.docker.com", "dockerhub-kspace") { |  | ||||||
|             build.push(label) |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
							
								
								
									
										19
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										19
									
								
								README.md
									
									
									
									
									
								
							| @@ -2,17 +2,13 @@ | |||||||
|  |  | ||||||
| This component serves allowed list of keyfob UID hashes from MongoDB and | This component serves allowed list of keyfob UID hashes from MongoDB and | ||||||
| pushes open door commands to door controllers. | pushes open door commands to door controllers. | ||||||
| We run three instances `replica{1..3}.doorboy.infra.k-space.ee` for high availability. |  | ||||||
| To connect from door controllers use round robin record `doorboy.infra.k-space.ee` |  | ||||||
|  |  | ||||||
|  |  | ||||||
| # Testing endpoints | # Testing endpoints | ||||||
|  |  | ||||||
| For manually testing something like this might help: | For manually testing something like this might help: | ||||||
|  |  | ||||||
| ``` | ``` | ||||||
| source .env | curl -f http://127.0.0.1:5000/allowed -H "KEY: 0123456789" | ||||||
| curl -f http://doorboy.infra.k-space.ee:5000/allowed -H "KEY: $DOORBOY_SECRET" | md5sum |  | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
| # Test scenarios | # Test scenarios | ||||||
| @@ -35,19 +31,8 @@ The easiest is to obtain VM from Proxmox cluster with public IP address. | |||||||
| To run development instance: | To run development instance: | ||||||
|  |  | ||||||
| ``` | ``` | ||||||
| docker-compose -f docker-compose.dev.yml up --build | docker-compose -f docker-compose.yml up --build | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
| On kdoorpi override `KDOORPI_API_ALLOWED`, `KDOORPI_API_LONGPOLL` environment variables | On kdoorpi override `KDOORPI_API_ALLOWED`, `KDOORPI_API_LONGPOLL` environment variables | ||||||
| to redirect requests to your dev instance. | to redirect requests to your dev instance. | ||||||
|  |  | ||||||
| # Deploying |  | ||||||
|  |  | ||||||
| Images are built and pushed to Docker Hub by Jenkins. |  | ||||||
| To deploy in prod adjust `DOORBOY_SECRET`, `MONGO_URI` in `.env` and proceed to launch: |  | ||||||
|  |  | ||||||
| ``` |  | ||||||
| overnode pull |  | ||||||
| overnode up |  | ||||||
| ``` |  | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										2
									
								
								const.py
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								const.py
									
									
									
									
									
								
							| @@ -1,2 +0,0 @@ | |||||||
| import os |  | ||||||
| MONGO_URI = os.getenv("MONGO_URI", "mongodb://127.0.0.1:27017/kspace_accounting") |  | ||||||
| @@ -1,23 +0,0 @@ | |||||||
| version: '3.7' |  | ||||||
|  |  | ||||||
| services: |  | ||||||
|   mongoexpress: |  | ||||||
|     image: mongo-express |  | ||||||
|     network_mode: host |  | ||||||
|     environment: |  | ||||||
|       - ME_CONFIG_MONGODB_ENABLE_ADMIN=true |  | ||||||
|       - ME_CONFIG_MONGODB_SERVER=127.0.0.1 |  | ||||||
|       - ME_CONFIG_MONGODB_PORT=27017 |  | ||||||
|       - ME_CONFIG_MONGODB_AUTH_DATABASE=admin |  | ||||||
|  |  | ||||||
|   mongo: |  | ||||||
|     network_mode: host |  | ||||||
|     image: mongo:latest |  | ||||||
|  |  | ||||||
|   doorboy_proxy: |  | ||||||
|     network_mode: host |  | ||||||
|     environment: |  | ||||||
|       MONGO_URI=mongodb://127.0.0.1:27017/kspace_accounting?replicaSet=kspace-mongo-set |  | ||||||
|       DOORBOY_SECRET=keykeykey |  | ||||||
|     build: |  | ||||||
|       context: . |  | ||||||
| @@ -1,17 +1,29 @@ | |||||||
| version: '3.7' | version: '3.7' | ||||||
|  |  | ||||||
| networks: |  | ||||||
|   infra: |  | ||||||
|     external: true |  | ||||||
|  |  | ||||||
| services: | services: | ||||||
|   app: |   mongoexpress: | ||||||
|     image: kspaceee/doorboy-proxy:latest |     image: mongo-express | ||||||
|     hostname: replica${OVERNODE_ID:-1}.doorboy.infra.k-space.ee |     network_mode: host | ||||||
|     restart: unless-stopped |     environment: | ||||||
|     env_file: .env |       - ME_CONFIG_MONGODB_ENABLE_ADMIN=true | ||||||
|     user: "65534:65534" |       - ME_CONFIG_MONGODB_SERVER=127.0.0.1 | ||||||
|     networks: |       - ME_CONFIG_MONGODB_PORT=27017 | ||||||
|       infra: |       - ME_CONFIG_MONGODB_AUTH_DATABASE=admin | ||||||
|         ipv4_address: 172.21.57.${OVERNODE_ID:-1} |     logging: | ||||||
|         ipv6_address: 2001:bb8:4008:21:57::${OVERNODE_ID:-1} |       driver: none | ||||||
|  |  | ||||||
|  |   mongo: | ||||||
|  |     network_mode: host | ||||||
|  |     image: mongo:latest | ||||||
|  |     volumes: | ||||||
|  |       - ./mongo-init.sh:/docker-entrypoint-initdb.d/mongo-init.sh:ro | ||||||
|  |     command: mongod --replSet rs0 --bind_ip 127.0.0.1 | ||||||
|  |     logging: | ||||||
|  |       driver: none | ||||||
|  |  | ||||||
|  |   doorboy_proxy: | ||||||
|  |     network_mode: host | ||||||
|  |     environment: | ||||||
|  |       DOORBOY_SECRET: 0123456789 | ||||||
|  |     build: | ||||||
|  |       context: . | ||||||
|   | |||||||
							
								
								
									
										57
									
								
								doorboy.py
									
									
									
									
									
								
							
							
						
						
									
										57
									
								
								doorboy.py
									
									
									
									
									
								
							| @@ -1,35 +1,43 @@ | |||||||
| from datetime import datetime | from sanic import Sanic | ||||||
| from sanic import Sanic, response | from sanic.response import text, json | ||||||
| from sanic.response import text, json, stream |  | ||||||
| from motor.motor_asyncio import AsyncIOMotorClient | from motor.motor_asyncio import AsyncIOMotorClient | ||||||
| import asyncio |  | ||||||
| import pymongo | import pymongo | ||||||
| import os | import os | ||||||
| import const |  | ||||||
|  |  | ||||||
| app = Sanic(__name__) | app = Sanic(__name__) | ||||||
|  |  | ||||||
| DOORBOY_SECRET = os.environ["DOORBOY_SECRET"] | DOORBOY_SECRET = os.environ["DOORBOY_SECRET"] | ||||||
|  | MONGO_URI = os.getenv("MONGO_URI", | ||||||
|  |                       "mongodb://127.0.0.1:27017/default?replicaSet=rs0") | ||||||
|  |  | ||||||
| assert len(DOORBOY_SECRET) > 10 | assert len(DOORBOY_SECRET) >= 10 | ||||||
|  |  | ||||||
|  |  | ||||||
| @app.listener("before_server_start") | @app.listener("before_server_start") | ||||||
| async def setup_db(app, loop): | async def setup_db(app, loop): | ||||||
|     # TODO: find cleaner way to do this, for more see |     # TODO: find cleaner way to do this, for more see | ||||||
|     # https://github.com/sanic-org/sanic/issues/919 |     # https://github.com/sanic-org/sanic/issues/919 | ||||||
|     app.db = AsyncIOMotorClient(const.MONGO_URI).get_default_database() |     app.ctx.db = AsyncIOMotorClient(MONGO_URI).get_default_database() | ||||||
|  |  | ||||||
|  |  | ||||||
| @app.route("/allowed") | @app.route("/allowed") | ||||||
| async def view_doorboy_uids(request): | async def view_doorboy_uids(request): | ||||||
|     if request.headers.get('KEY') != DOORBOY_SECRET: |     if request.headers.get("KEY") != DOORBOY_SECRET: | ||||||
|         return text("how about no") |         return text("how about no") | ||||||
|     allowed_names = [] |     allowed_names = [] | ||||||
|     async for obj in app.db.member.find({"enabled": True}): |     async for obj in app.ctx.db.member.find({"enabled": True}): | ||||||
|         allowed_names.append(obj["_id"]) |         allowed_names.append(obj["_id"]) | ||||||
|     allowed_uids = [] |     allowed_uids = [] | ||||||
|     async for obj in app.db.inventory.find({"token.uid_hash": {"$exists":True}, "inventory.owner": {"$exists":True}, "token.enabled": {"$exists":True}}, {"inventory.owner": True, "token.uid_hash": True }): |     flt = { | ||||||
|  |         "token.uid_hash": {"$exists": True}, | ||||||
|  |         "inventory.owner": {"$exists": True}, | ||||||
|  |         "token.enabled": {"$exists": True} | ||||||
|  |     } | ||||||
|  |     prj = { | ||||||
|  |         "inventory.owner": True, | ||||||
|  |         "token.uid_hash": True | ||||||
|  |     } | ||||||
|  |     async for obj in app.ctx.db.inventory.find(flt, prj): | ||||||
|         if obj["inventory"].pop("owner").get("foreign_id") in allowed_names: |         if obj["inventory"].pop("owner").get("foreign_id") in allowed_names: | ||||||
|             del obj["_id"] |             del obj["_id"] | ||||||
|             del obj["inventory"] |             del obj["inventory"] | ||||||
| @@ -37,31 +45,30 @@ async def view_doorboy_uids(request): | |||||||
|     return json({"allowed_uids": allowed_uids}) |     return json({"allowed_uids": allowed_uids}) | ||||||
|  |  | ||||||
|  |  | ||||||
| @app.route("/longpoll") | @app.route("/longpoll", stream=True) | ||||||
| async def view_longpoll(request): | async def view_longpoll(request): | ||||||
|     if request.headers.get('KEY') != DOORBOY_SECRET: |     response = await request.respond(content_type="text/event-stream") | ||||||
|         return text("how about no") |     if request.headers.get("KEY") != DOORBOY_SECRET: | ||||||
|  |         return text("Invalid token") | ||||||
|  |  | ||||||
|     async def g(response): |     await response.send("data: response-generator-started\n\n") | ||||||
|         await response.write("data: response-generator-started\n\n") |  | ||||||
|     pipeline = [ |     pipeline = [ | ||||||
|         { |         { | ||||||
|                 '$match': { |             "$match": { | ||||||
|                      'operationType': "insert", |                  "operationType": "insert", | ||||||
| #                     'component': 'doorboy', |  | ||||||
| #                     'type': 'open-door' |  | ||||||
|              } |              } | ||||||
|         } |         } | ||||||
|     ] |     ] | ||||||
|     try: |     try: | ||||||
|             async with app.db.eventlog.watch(pipeline) as stream: |         async with app.ctx.db.eventlog.watch(pipeline) as stream: | ||||||
|                 await response.write("data: watch-stream-opened\n\n") |             await response.send("data: watch-stream-opened\n\n") | ||||||
|             async for event in stream: |             async for event in stream: | ||||||
|                 if event["fullDocument"].get("type") == "open-door": |                 if event["fullDocument"].get("type") == "open-door": | ||||||
|                         await response.write("data: %s\n\n" % event["fullDocument"]["door"]) |                     await response.send("data: %s\n\n" % | ||||||
|  |                                         event["fullDocument"]["door"]) | ||||||
|     except pymongo.errors.PyMongoError: |     except pymongo.errors.PyMongoError: | ||||||
|         return |         return | ||||||
|     return stream(g, content_type="text/event-stream") |  | ||||||
|  |  | ||||||
| if __name__ == '__main__': |  | ||||||
|     app.run(debug=False, host='0.0.0.0', port=5000) | if __name__ == "__main__": | ||||||
|  |     app.run(debug=False, host="0.0.0.0", port=5000) | ||||||
|   | |||||||
							
								
								
									
										9
									
								
								mongo-init.sh
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								mongo-init.sh
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,9 @@ | |||||||
|  | #!/bin/bash | ||||||
|  | mongo <<EOF | ||||||
|  | rs.initiate({ | ||||||
|  |     _id: 'rs0', | ||||||
|  |     members: [ | ||||||
|  |         {_id: 0, host: '127.0.0.1:27017'} | ||||||
|  |     ] | ||||||
|  | }) | ||||||
|  | EOF | ||||||
| @@ -1,6 +0,0 @@ | |||||||
| id: doorboy |  | ||||||
|  |  | ||||||
| version: 3.7 |  | ||||||
|  |  | ||||||
| app: |  | ||||||
|     docker-compose.yml: 1, 2, 3 |  | ||||||
		Reference in New Issue
	
	Block a user