commit e7041b2ccdb184ba6f0c621dec336605f8f55822 Author: marcsello Date: Fri Apr 16 19:53:02 2021 +0200 Initial commit diff --git a/.drone.yml b/.drone.yml new file mode 100644 index 0000000..a248b46 --- /dev/null +++ b/.drone.yml @@ -0,0 +1,25 @@ +kind: pipeline +type: docker +name: default +steps: + + - name: code-analysis + image: aosapps/drone-sonar-plugin + settings: + sonar_host: + from_secret: SONAR_HOST + sonar_token: + from_secret: SONAR_CODE + + - name: kaniko + image: banzaicloud/drone-kaniko + settings: + registry: registry.kmlabz.com + repo: universalrobots/${DRONE_REPO_NAME} + username: + from_secret: DOCKER_USERNAME + password: + from_secret: DOCKER_PASSWORD + tags: + - latest + - ${DRONE_BUILD_NUMBER} \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..53fd5d2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +*.swp +venv +*.pyc +__pycache__/* +__pycache__ +*.wpr +*.log +.idea diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..dd780f8 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,10 @@ +FROM python:3.9 + +ADD job_orchestrator_service requirements.txt /job_orchestrator_service/ +WORKDIR /job_orchestrator_service/ + +RUN pip3 install -r requirements.txt && pip3 install gunicorn + +EXPOSE 8000 +CMD ["gunicorn", "-b", "0.0.0.0:8000", "app:app"] + diff --git a/job_orchestrator_service/app.py b/job_orchestrator_service/app.py new file mode 100644 index 0000000..4abb887 --- /dev/null +++ b/job_orchestrator_service/app.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python3 +from config import Config +import sentry_sdk +from sentry_sdk.integrations.flask import FlaskIntegration +from flask import Flask + +from utils import register_all_error_handlers + +# import views +from views import JobView + +# Setup sentry + +if Config.SENTRY_DSN: + sentry_sdk.init( + dsn=Config.SENTRY_DSN, + integrations=[FlaskIntegration()], + send_default_pii=True, + release=Config.RELEASE_ID, + environment=Config.RELEASEMODE + ) + +# create flask app +app = Flask(__name__) +app.config.from_object(Config) +register_all_error_handlers(app) + +# register views +for view in [JobView]: + view.register(app, trailing_slash=False) + +# start debuggig if needed +if __name__ == "__main__": + app.run(debug=True) diff --git a/job_orchestrator_service/config.py b/job_orchestrator_service/config.py new file mode 100644 index 0000000..532830c --- /dev/null +++ b/job_orchestrator_service/config.py @@ -0,0 +1,15 @@ +#!/usr/bin/env python3 +import os + +""" +Configuration +""" + + +class Config: + SECRET_KEY = os.environ.get("SECRET_KEY", os.urandom(12)) + CORS_ORIGINS = os.environ.get("ALLOWED_ORIGINS", "*") + + SENTRY_DSN = os.environ.get("SENTRY_DSN") + RELEASE_ID = os.environ.get("RELEASE_ID", "test") + RELEASEMODE = os.environ.get("RELEASEMODE", "dev") diff --git a/job_orchestrator_service/schemas/__init__.py b/job_orchestrator_service/schemas/__init__.py new file mode 100644 index 0000000..0c7a1cb --- /dev/null +++ b/job_orchestrator_service/schemas/__init__.py @@ -0,0 +1,2 @@ +#!/usr/bin/env python3 +from .job_schema import JobSchema diff --git a/job_orchestrator_service/schemas/job_schema.py b/job_orchestrator_service/schemas/job_schema.py new file mode 100644 index 0000000..dad7ea6 --- /dev/null +++ b/job_orchestrator_service/schemas/job_schema.py @@ -0,0 +1,22 @@ +from marshmallow import Schema, fields +from marshmallow.validate import Length +from marshmallow import RAISE + +from datetime import datetime +import uuid + + +class ControlConfigurationSchema(Schema): + pod_id = fields.UUID(required=False, missing=uuid.uuid4) + robot_address = fields.Str(required=True) + program_url = fields.Str(required=True) + + +class JobSchema(Schema): + id = fields.UUID(required=False, missing=uuid.uuid4) + created_at = fields.DateTime(required=False, missing=datetime.now) + + controllers = fields.Nested(ControlConfigurationSchema, many=True, required=True, validate=Length(min=1)) + + class Meta: + unknown = RAISE diff --git a/job_orchestrator_service/utils/__init__.py b/job_orchestrator_service/utils/__init__.py new file mode 100644 index 0000000..6c6531e --- /dev/null +++ b/job_orchestrator_service/utils/__init__.py @@ -0,0 +1,3 @@ +#!/usr/bin/env python3 +from .require_decorators import json_required +from .error_handlers import register_all_error_handlers diff --git a/job_orchestrator_service/utils/error_handlers.py b/job_orchestrator_service/utils/error_handlers.py new file mode 100644 index 0000000..959f8ae --- /dev/null +++ b/job_orchestrator_service/utils/error_handlers.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 + + +def get_standard_error_handler(code: int): + def error_handler(err): + return {"msg": str(err)}, code + + return error_handler + + +# function to register all handlers + + +def register_all_error_handlers(app): + error_codes_to_override = [404, 403, 401, 405, 400, 409, 422] + + for code in error_codes_to_override: + app.register_error_handler(code, get_standard_error_handler(code)) diff --git a/job_orchestrator_service/utils/require_decorators.py b/job_orchestrator_service/utils/require_decorators.py new file mode 100644 index 0000000..ba5b9ab --- /dev/null +++ b/job_orchestrator_service/utils/require_decorators.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python3 +from flask import request, current_app, abort + +from functools import wraps + + +def json_required(f): + @wraps(f) + def call(*args, **kwargs): + + if request.is_json: + return f(*args, **kwargs) + else: + abort(400, "JSON required") + + return call diff --git a/job_orchestrator_service/views/__init__.py b/job_orchestrator_service/views/__init__.py new file mode 100644 index 0000000..ff3bb93 --- /dev/null +++ b/job_orchestrator_service/views/__init__.py @@ -0,0 +1,3 @@ +#!/usr/bin/env python3 +from .job_view import JobView + diff --git a/job_orchestrator_service/views/job_view.py b/job_orchestrator_service/views/job_view.py new file mode 100644 index 0000000..8a60d24 --- /dev/null +++ b/job_orchestrator_service/views/job_view.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python3 +from flask import request, jsonify, current_app, abort, Response +from flask_classful import FlaskView, route + +from utils import json_required + +from marshmallow.exceptions import ValidationError + +from schemas import JobSchema + + +class JobView(FlaskView): + job_schema = JobSchema(many=False) + jobs_schema = JobSchema(many=True, exclude=['controllers']) + + def index(self): + # List all jobs + pass + + def get(self, _id: str): + # Get info about a job + pass + + @json_required + def post(self): + # Start (schedule) a job + pass + + def delete(self, _id: str): + # stop a job + pass diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..db7344b --- /dev/null +++ b/requirements.txt @@ -0,0 +1,7 @@ +pyyaml + +blinker +Flask +marshmallow +Flask-Classful +sentry-sdk \ No newline at end of file