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 | ||||
| WORKDIR /app | ||||
|  | ||||
| COPY requirements.txt ./ | ||||
| RUN pip install -r requirements.txt | ||||
|  | ||||
| COPY *.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 | ||||
| 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 | ||||
|  | ||||
| For manually testing something like this might help: | ||||
|  | ||||
| ``` | ||||
| source .env | ||||
| curl -f http://doorboy.infra.k-space.ee:5000/allowed -H "KEY: $DOORBOY_SECRET" | md5sum | ||||
| curl -f http://127.0.0.1:5000/allowed -H "KEY: 0123456789" | ||||
| ``` | ||||
|  | ||||
| # Test scenarios | ||||
| @@ -35,19 +31,8 @@ The easiest is to obtain VM from Proxmox cluster with public IP address. | ||||
| 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 | ||||
| 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' | ||||
|  | ||||
| networks: | ||||
|   infra: | ||||
|     external: true | ||||
|  | ||||
| services: | ||||
|   app: | ||||
|     image: kspaceee/doorboy-proxy:latest | ||||
|     hostname: replica${OVERNODE_ID:-1}.doorboy.infra.k-space.ee | ||||
|     restart: unless-stopped | ||||
|     env_file: .env | ||||
|     user: "65534:65534" | ||||
|     networks: | ||||
|       infra: | ||||
|         ipv4_address: 172.21.57.${OVERNODE_ID:-1} | ||||
|         ipv6_address: 2001:bb8:4008:21:57::${OVERNODE_ID:-1} | ||||
|   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 | ||||
|     logging: | ||||
|       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, response | ||||
| from sanic.response import text, json, stream | ||||
| from sanic import Sanic | ||||
| from sanic.response import text, json | ||||
| from motor.motor_asyncio import AsyncIOMotorClient | ||||
| import asyncio | ||||
| import pymongo | ||||
| import os | ||||
| import const | ||||
|  | ||||
| app = Sanic(__name__) | ||||
|  | ||||
| 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") | ||||
| async def setup_db(app, loop): | ||||
|     # TODO: find cleaner way to do this, for more see | ||||
|     # 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") | ||||
| 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") | ||||
|     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_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: | ||||
|             del obj["_id"] | ||||
|             del obj["inventory"] | ||||
| @@ -37,31 +45,30 @@ async def view_doorboy_uids(request): | ||||
|     return json({"allowed_uids": allowed_uids}) | ||||
|  | ||||
|  | ||||
| @app.route("/longpoll") | ||||
| @app.route("/longpoll", stream=True) | ||||
| async def view_longpoll(request): | ||||
|     if request.headers.get('KEY') != DOORBOY_SECRET: | ||||
|         return text("how about no") | ||||
|     response = await request.respond(content_type="text/event-stream") | ||||
|     if request.headers.get("KEY") != DOORBOY_SECRET: | ||||
|         return text("Invalid token") | ||||
|  | ||||
|     async def g(response): | ||||
|         await response.write("data: response-generator-started\n\n") | ||||
|     await response.send("data: response-generator-started\n\n") | ||||
|     pipeline = [ | ||||
|         { | ||||
|                 '$match': { | ||||
|                      'operationType': "insert", | ||||
| #                     'component': 'doorboy', | ||||
| #                     'type': 'open-door' | ||||
|             "$match": { | ||||
|                  "operationType": "insert", | ||||
|              } | ||||
|         } | ||||
|     ] | ||||
|     try: | ||||
|             async with app.db.eventlog.watch(pipeline) as stream: | ||||
|                 await response.write("data: watch-stream-opened\n\n") | ||||
|         async with app.ctx.db.eventlog.watch(pipeline) as stream: | ||||
|             await response.send("data: watch-stream-opened\n\n") | ||||
|             async for event in stream: | ||||
|                 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: | ||||
|         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