Gogs 7 éve
szülő
commit
86069352f5
48 módosított fájl, 1634 hozzáadás és 0 törlés
  1. 3 0
      .gitmodules
  2. 21 0
      eiru-automation/app/.env
  3. 6 0
      eiru-automation/app/.gitignore
  4. 3 0
      eiru-automation/app/.gitmodules
  5. 0 0
      eiru-automation/app/api/__init__.py
  6. 8 0
      eiru-automation/app/api/apps.py
  7. BIN
      eiru-automation/app/api/files/odoo.tar
  8. 1 0
      eiru-automation/app/api/resources/__init__.py
  9. 125 0
      eiru-automation/app/api/resources/docker_resource.py
  10. 13 0
      eiru-automation/app/api/resources/group_resource.py
  11. 86 0
      eiru-automation/app/api/resources/jwt_resource.py
  12. 191 0
      eiru-automation/app/api/resources/odoo_resource.py
  13. 14 0
      eiru-automation/app/api/resources/permission_resource.py
  14. 38 0
      eiru-automation/app/api/resources/request_resource.py
  15. 12 0
      eiru-automation/app/api/resources/task_resource.py
  16. 107 0
      eiru-automation/app/api/resources/user_resource.py
  17. 42 0
      eiru-automation/app/api/templates/openerp-server.j2
  18. 23 0
      eiru-automation/app/api/urls.py
  19. 0 0
      eiru-automation/app/api/utils/__init__.py
  20. 28 0
      eiru-automation/app/api/utils/command.py
  21. 201 0
      eiru-automation/app/api/utils/docker_api.py
  22. 52 0
      eiru-automation/app/api/utils/jwt_authentication.py
  23. 93 0
      eiru-automation/app/api/utils/jwt_token.py
  24. 177 0
      eiru-automation/app/api/utils/odoo_api.py
  25. 0 0
      eiru-automation/app/api/validations/__init__.py
  26. 45 0
      eiru-automation/app/api/validations/user_validation.py
  27. 0 0
      eiru-automation/app/core/__init__.py
  28. 8 0
      eiru-automation/app/core/apps.py
  29. 48 0
      eiru-automation/app/core/migrations/0001_initial.py
  30. 0 0
      eiru-automation/app/core/migrations/__init__.py
  31. 4 0
      eiru-automation/app/core/models/__init__.py
  32. 13 0
      eiru-automation/app/core/models/base.py
  33. 20 0
      eiru-automation/app/core/models/request.py
  34. 11 0
      eiru-automation/app/core/models/task.py
  35. 22 0
      eiru-automation/app/manage.py
  36. 0 0
      eiru-automation/app/odoo_control/__init__.py
  37. 102 0
      eiru-automation/app/odoo_control/settings.py
  38. 13 0
      eiru-automation/app/odoo_control/urls.py
  39. 16 0
      eiru-automation/app/odoo_control/wsgi.py
  40. 38 0
      eiru-automation/app/requirements.txt
  41. 0 0
      eiru-automation/app/ui/__init__.py
  42. 6 0
      eiru-automation/app/ui/admin.py
  43. 8 0
      eiru-automation/app/ui/apps.py
  44. 0 0
      eiru-automation/app/ui/migrations/__init__.py
  45. 9 0
      eiru-automation/app/ui/package.json
  46. 15 0
      eiru-automation/app/ui/templates/index.html
  47. 8 0
      eiru-automation/app/ui/views.py
  48. 4 0
      eiru-automation/app/yarn.lock

+ 3 - 0
.gitmodules

@@ -0,0 +1,3 @@
+[submodule "app"]
+	path = eiru-automation/app
+	url = http://192.168.88.100:3000/robert/eiru_automation.git

+ 21 - 0
eiru-automation/app/.env

@@ -0,0 +1,21 @@
+SECRET_KEY = '^*&s%i#9p7tq(#%f)--#ki2*qx8=iv5173eg35$qupv8%+fyv7'
+ALLOWED_HOSTS = *
+DEBUG = True
+JWT_ACCEPT_HEADER = 'HTTP_AUTHORIZATION'
+JWT_PREFIX_HEADER = 'JWT'
+JWT_SECRET_KEY = '123456789'
+SERVER_IP = '192.168.88.100'
+DOCKER_SOCK_DIR = '/var/run/docker.sock'
+DOCKER_NETWORK = '127.0.0.1'
+DOCKER_NETWORK_NAME = 'eiru'
+ODOO_IMAGE = 'odoo/robert:8.0'
+ODOO_ROOT_PATH = '/opt/odoo/'
+ODOO_DEFAULT_FOLDERS = 'config: /etc/odoo, custom-addons: /mnt/extra-addons, files: /var/lib/odoo'
+ODOO_PORTS_RANGE = '10000, 30000'
+ODOO_CONF_FILENAME = 'openerp-server'
+ODOO_ADMIN_PASSWORD = 'admin'
+ODOO_DB_CONTAINER = 'postgres'
+ODOO_DB_HOST = '172.20.0.2'
+ODOO_DB_PORT = '5432'
+ODOO_DB_USER = 'odoo'
+ODOO_DB_PASSWORD = 'odoo'

+ 6 - 0
eiru-automation/app/.gitignore

@@ -0,0 +1,6 @@
+*.pyc
+.sonarlint
+*.db
+ui/node_modules
+ui/yarn*
+ui/static/*

+ 3 - 0
eiru-automation/app/.gitmodules

@@ -0,0 +1,3 @@
+[submodule "eiru_automation_ui"]
+	path = eiru_automation_ui
+	url = http://192.168.88.100:3000/robert/eiru_automation_ui.git

+ 0 - 0
eiru-automation/app/api/__init__.py


+ 8 - 0
eiru-automation/app/api/apps.py

@@ -0,0 +1,8 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.apps import AppConfig
+
+
+class ApiConfig(AppConfig):
+    name = 'api'

BIN
eiru-automation/app/api/files/odoo.tar


+ 1 - 0
eiru-automation/app/api/resources/__init__.py

@@ -0,0 +1 @@
+# -*- coding: utf-8 -*-

+ 125 - 0
eiru-automation/app/api/resources/docker_resource.py

@@ -0,0 +1,125 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+from django.conf.urls import url
+from tastypie.resources import Resource
+from tastypie.utils import trailing_slash
+from api.utils.jwt_authentication import JWTAuthentication
+from api.utils.docker_api import (
+    get_all_images, 
+    get_all_containers, 
+    start_container, 
+    restart_container,
+    stop_container
+)
+
+'''
+'''
+class DockerResource(Resource):
+    class Meta:
+        authentication = JWTAuthentication()
+
+    '''
+    '''
+    def prepend_urls(self):
+        return [
+            url(r'^(?P<resource_name>%s)/image/all%s$' % (self._meta.resource_name, trailing_slash), self.wrap_view('get_all_images'), name='api_get_all_images'),
+            url(r'^(?P<resource_name>%s)/container/all%s$' % (self._meta.resource_name, trailing_slash), self.wrap_view('get_all_containers'), name='api_get_all_containers'),
+            url(r'^(?P<resource_name>%s)/container/start/(?P<container_id>[\w\d_.-]+)%s$' % (self._meta.resource_name, trailing_slash), self.wrap_view('start_container'), name='api_start_container'),
+            url(r'^(?P<resource_name>%s)/container/restart/(?P<container_id>[\w\d_.-]+)%s$' % (self._meta.resource_name, trailing_slash), self.wrap_view('restart_container'), name='api_restart_container'),
+            url(r'^(?P<resource_name>%s)/container/stop/(?P<container_id>[\w\d_.-]+)%s$' % (self._meta.resource_name, trailing_slash), self.wrap_view('stop_container'), name='api_stop_container'),
+        ]
+
+    '''
+    '''
+    def get_all_images(self, request, **kwargs):
+        self.method_check(request, allowed='get')
+        self.is_authenticated(request)
+
+        bundle = self.build_bundle(obj={
+            'images': get_all_images()
+        }, request=request)
+
+        return self.create_response(request, bundle.obj)
+
+    '''
+    '''
+    def get_all_containers(self, request, **kwargs):
+        self.method_check(request, allowed='get')
+        self.is_authenticated(request)
+
+        return self.create_response(request, {
+            'containers': get_all_containers()
+        })
+
+    '''
+    '''
+    def start_container(self, request, **kwargs):
+        self.method_check(request, allowed='post')
+        self.is_authenticated(request)
+
+        container_id = kwargs.get('container_id', None)
+
+        if not container_id:
+            return self.create_response(request, {
+                'error_message': 'container id is required'
+            }, request=request)
+
+        container_data = start_container(container_id)
+
+        if not container_data:
+            return self.create_response(request, {
+                'error_message': 'cannot start container %s' % container_id
+            }, request=request)
+
+
+        return self.create_response(request, {
+            'container': container_data
+        })
+
+    '''
+    '''
+    def restart_container(self, request, **kwargs):
+        self.method_check(request, allowed='post')
+        self.is_authenticated(request)
+
+        container_id = kwargs.get('container_id', None)
+
+        if not container_id:
+            return self.create_response({
+                'error_message': 'container id is required'
+            }, request=request)
+
+        container_data = restart_container(container_id)
+
+        if not container_data:
+            return self.create_response({
+                'error_message': 'cannot restart container %s' % container_id
+            })
+
+        return self.create_response(request, {
+            'container': container_data
+        })
+
+    '''
+    '''
+    def stop_container(self, request, **kwargs):
+        self.method_check(request, allowed='post')
+        self.is_authenticated(request)
+
+        container_id = kwargs.get('container_id', None)
+
+        if not container_id:
+            return self.create_response({
+                'error_message': 'container id is required'
+            })
+
+        container_data = stop_container(container_id)
+
+        if not container_data:
+            return self.create_response({
+                'error_message': 'cannot stop container %s' % container_id
+            })
+            
+        return self.create_response(request, {
+            'container': container_data
+        })

+ 13 - 0
eiru-automation/app/api/resources/group_resource.py

@@ -0,0 +1,13 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+from tastypie.resources import ModelResource
+from api.utils.jwt_authentication import JWTAuthentication
+from django.contrib.auth.models import Group
+
+'''
+'''
+class GroupResource(ModelResource):
+    class Meta:
+        queryset = Group.objects.all()
+        always_return_data = True
+        authentication = JWTAuthentication()

+ 86 - 0
eiru-automation/app/api/resources/jwt_resource.py

@@ -0,0 +1,86 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+from django.conf.urls import url
+from tastypie import http
+from tastypie.resources import Resource
+from tastypie.utils import trailing_slash
+from tastypie.exceptions import ImmediateHttpResponse
+from api.utils import jwt_token
+import simplejson as json
+
+'''
+'''
+class JWTResource(Resource):
+    class Meta:
+        allowed_methods = ['post']
+        resource_name = 'auth'
+
+    '''
+    '''
+    def prepend_urls(self):
+        return [
+            url(r'^%s/get_token%s$' % (self._meta.resource_name, trailing_slash), self.wrap_view('get_token'), name="api_get_token"),
+            url(r'^%s/check_token%s$' % (self._meta.resource_name, trailing_slash), self.wrap_view('check_token'), name="api_check_token"),
+        ]
+
+    '''
+    '''
+    def get_token(self, request, **kwargs):
+        self.method_check(request, allowed=self._meta.allowed_methods)
+
+        # Check content type
+        if request.content_type != 'application/json':
+            raise ImmediateHttpResponse(response=http.HttpUnauthorized())
+
+        # Check body
+        if not request.body:
+           raise ImmediateHttpResponse(response=http.HttpUnauthorized())
+
+        # Check required parameters
+        body = json.loads(request.body)
+        if 'username' not in body or 'password' not in body:
+            raise ImmediateHttpResponse(response=http.HttpUnauthorized())
+
+        # Check user
+        token = jwt_token.create_token(body['username'], body['password'])
+        if not token:
+            raise ImmediateHttpResponse(response=http.HttpUnauthorized())
+
+        bundle = self.build_bundle(obj={
+            'token': token,
+            'username': body['username']
+        }, request=request)
+
+        return self.create_response(request, bundle.obj)
+
+    '''
+    '''
+    def check_token(self, request, **kwargs):
+        self.method_check(request, allowed=self._meta.allowed_methods)
+
+         # Check content type
+        if request.content_type != 'application/json':
+            raise ImmediateHttpResponse(response=http.HttpUnauthorized())
+
+        # Check body
+        if not request.body:
+            raise ImmediateHttpResponse(response=http.HttpUnauthorized())
+
+        # Check required parameters
+        body = json.loads(request.body)
+        if 'token' not in body:
+            raise ImmediateHttpResponse(response=http.HttpUnauthorized())
+
+        (user, ok) = jwt_token.check_token(body['token'])
+
+        # Check status
+        response_status = (401, 200)[bool(ok)]
+        if response_status == 401:
+            raise ImmediateHttpResponse(response=http.HttpUnauthorized())
+
+        bundle = self.build_bundle(obj={
+            'token': body['token'],
+            'username': user.username
+        }, request=request)
+
+        return self.create_response(request, bundle.obj)

+ 191 - 0
eiru-automation/app/api/resources/odoo_resource.py

@@ -0,0 +1,191 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+from django.conf.urls import url
+from django.conf import settings
+from tastypie.resources import Resource
+from tastypie.utils import trailing_slash
+from core.models.request import Request
+from api.utils.odoo_api import (
+    normalize_name,
+    check_name,
+    randomize_port,
+    create_folders,
+    create_configuration,
+    create_database,
+    copy_database_seed,
+    restore_database,
+    remove_database_seed,
+    create_odoo_container,
+    apply_permissions
+)
+from api.utils.jwt_token import get_user
+from api.utils.jwt_authentication import JWTAuthentication
+from simplejson import JSONDecodeError
+import simplejson as json
+
+class OdooResource(Resource):
+    class Meta:
+        authentication = JWTAuthentication()
+
+    '''
+    '''
+    def prepend_urls(self):
+        return [
+            url(r'^(?P<resource_name>%s)/create%s$' % (self._meta.resource_name, trailing_slash), self.wrap_view('odoo_create'), name='api_odoo_create'),
+        ]
+    
+    '''
+    '''
+    def odoo_create(self, request, **kwargs):
+        self.method_check(request, allowed='post')
+        self.is_authenticated(request)
+
+        authorization_header = request.META.get(settings.JWT_ACCEPT_HEADER)
+        prefix_length = len(settings.JWT_PREFIX_HEADER)
+        user = get_user(authorization_header[prefix_length + 1:])
+
+        name = None
+        try:
+            data = json.loads(request.body)
+            name = data['name']
+        except JSONDecodeError:
+            name = None
+        except KeyError:
+            name = None
+
+        r = Request.objects.create(name='Crear contenedor Odoo')
+        r.user = user
+
+        if not name:
+            r.issue = 'name is required'
+            r.status = 5
+            r.save()
+
+            return self.create_response(request, {
+                'error_message': 'name is required'
+            })
+
+        # 1. Check and get name
+        name = normalize_name(name)
+        name_exists = check_name(name)
+
+        if name_exists:
+            r.issue = '%s: name is already exists' % name
+            r.status = 5
+            r.save()
+
+            return self.create_response(request, {
+                'error_message': '%s: name is already exists' % name
+            })
+        
+        # 2. Get port
+        port = randomize_port()
+
+        # 3. Folder structure create
+        folders_created = create_folders(name)
+
+        if not folders_created:
+            r.issue = '%s: folders structure cannot be created' % name
+            r.status = 5
+            r.save()
+
+            return self.create_response(request, {
+                'error_message': '%s: folders structure cannot be created' % name
+            })
+
+        # 4. Configuration create
+        conf_created = create_configuration(name)
+
+        if not conf_created:
+            r.issue = '%s: configuration cannot be created' % name
+            r.status = 5
+            r.save()
+
+            return self.create_response(request, {
+                'error_message': '%s: configuration cannot be created' % name
+            })
+
+        # 5. Database create
+        db_created = create_database(name)
+
+        if not db_created:
+            r.issue = '%s: database cannot be created' % name
+            r.status = 5
+            r.save()
+
+            return self.create_response(request, {
+                'error_message': '%s: database cannot be created' % name
+            })
+
+        # 6. Copy a database backup
+        seed_copied = copy_database_seed()
+
+        if not seed_copied:
+            r.issue = '%s: database seed cannot be copied' % name
+            r.status = 5
+            r.save()
+
+            return self.create_response(request, {
+                'error_message': '%s: database seed cannot be copied' % name
+            })
+        
+        # 7. Restore database schema 
+        db_restored = restore_database(name)
+        
+        if not db_restored:
+            r.issue = '%s: database cannot be restored' % name
+            r.status = 5
+            r.save()
+
+            return self.create_response(request, {
+                'error_message': '%s: database cannot be restored' % name
+            })
+
+        # 8. Remove unused bakup file
+        seed_removed = remove_database_seed()
+
+        if not seed_removed:
+            r.issue = '%s: seed cannot be removed' % name
+            r.status = 5
+            r.save()
+
+            return self.create_response(request, {
+                'error_message': '%s: seed cannot be removed' % name
+            })
+
+
+        # 9. Odoo create
+        odoo_created = create_odoo_container(name, [port])
+
+        if not odoo_created:
+            r.issue = '%s: odoo container cannot be created' % name
+            r.status = 5
+            r.save()
+
+            return self.create_response(request, {
+                'error_message': '%s: odoo container cannot be created' % name
+            })
+
+        # 10. Apply permissions 
+        permissions_applied = apply_permissions(name)
+
+        if not permissions_applied:
+            r.issue = '%s: odoo container cannot be started' % name
+            r.status = 5
+            r.save()
+
+            return self.create_response(request, {
+                'error_message': '%s: odoo container cannot be started' % name
+            })
+
+        r.issue = '%s: odoo container created successfully' % name
+        r.status = 4
+        r.save()
+        
+        return self.create_response(request, {
+            'action': {
+                'type': 'redirect',
+                'ip': settings.SERVER_IP,
+                'port': port
+            }
+        })

+ 14 - 0
eiru-automation/app/api/resources/permission_resource.py

@@ -0,0 +1,14 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+from tastypie.resources import ModelResource
+from tastypie.authorization import Authorization
+from api.utils.jwt_authentication import JWTAuthentication
+from django.contrib.auth.models import Permission
+
+'''
+'''
+class PermissionResource(ModelResource):
+    class Meta:
+        queryset = Permission.objects.all()
+        always_return_data = True
+        authentication = JWTAuthentication()

+ 38 - 0
eiru-automation/app/api/resources/request_resource.py

@@ -0,0 +1,38 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+from tastypie import fields
+from tastypie.resources import ModelResource
+from tastypie.authorization import Authorization
+from core.models.request import Request
+from api.resources.user_resource import UserResource
+from datetime import datetime
+import pytz
+
+DATETIME_FORMAT = '%Y-%m-%d %H:%M:%S'
+
+'''
+'''
+class RequestResource(ModelResource):
+    user = fields.ToOneField(UserResource, 'user', full=True, null=True)
+
+    class Meta:
+        queryset = Request.objects.all()
+        authorization = Authorization()
+        always_return_data = True,
+        collection_name = 'requests'
+    
+    '''
+    '''
+    def dehydrate_status(self, bundle):
+        return [
+            bundle.obj.status,
+            bundle.obj.get_status_display()
+        ]
+
+    '''
+    '''
+    def dehydrate_create_at(self, bundle):
+        dt = bundle.obj.create_at
+        dt.replace(tzinfo=pytz.UTC)
+        return dt.strftime(DATETIME_FORMAT)
+

+ 12 - 0
eiru-automation/app/api/resources/task_resource.py

@@ -0,0 +1,12 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+from tastypie.resources import ModelResource
+from api.utils.jwt_authentication import JWTAuthentication
+from core.models.task import Task
+
+'''
+'''
+class TaskResource(ModelResource):
+    class Meta:
+        queryset = Task.objects.all()
+        authentication = JWTAuthentication()

+ 107 - 0
eiru-automation/app/api/resources/user_resource.py

@@ -0,0 +1,107 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+from tastypie import fields
+from tastypie.resources import ModelResource
+from tastypie.authorization import Authorization
+from tastypie.exceptions import ImmediateHttpResponse
+from tastypie.utils import trailing_slash
+from django.conf.urls import url
+from django.contrib.auth.models import User
+from api.validations.user_validation import UserValidation
+from api.resources.group_resource import GroupResource
+from api.utils.jwt_authentication import JWTAuthentication
+import simplejson as json
+
+'''
+'''
+class UserResource(ModelResource):
+    groups = fields.ToManyField(GroupResource, 'groups')
+
+    class Meta:
+        queryset = User.objects.all()
+        always_return_data = True
+        validation = UserValidation()
+        authentication = JWTAuthentication()
+
+    '''
+    '''
+    def prepend_urls(self):
+        return [
+            url(r'^(?P<resource_name>%s)/(?P<%s>.*?)/change_password%s$' % (self._meta.resource_name, self._meta.detail_uri_name, trailing_slash), self.wrap_view('change_password'), name='api_change_password')
+        ]
+
+    '''
+    '''
+    def change_password(self, request, **kwargs):
+        self.method_check(request, ['post'])
+        self.is_authenticated(request)
+
+        # Check content type
+        if request.content_type != 'application/json':
+            return self.create_response(request, {
+                'error_message': 'request is not json'
+            })
+
+        # Check body
+        if not request.body:
+            return self.create_response(request, {
+                'error_message': 'request body is empty'
+            })
+        
+        # Check if password is present in body
+        if not 'password' in request.body:
+            return self.create_response(request, {
+                'error_message': 'password is not provided'
+            })
+
+        body = json.loads(request.body)
+        user = User.objects.get(pk=kwargs.get('pk'))
+
+        user.set_password(body['password'])
+
+        bundle = self.build_bundle(obj=user, request=request)
+        bundle = self.full_dehydrate(bundle)
+
+        return self.create_response(request, {
+            self._meta.resource_name: bundle
+        })
+
+    '''
+    '''
+    def save(self, bundle, skip_errors=False):
+        if bundle.via_uri:
+            return bundle
+
+        self.is_valid(bundle)
+
+        # If bundle has errors send this reponse
+        if bundle.errors and not skip_errors:
+            raise ImmediateHttpResponse(response=self.error_response(bundle.request, bundle.errors))
+
+        # If object data is not persist create django auth user
+        if bundle.obj._state.adding:
+            user = User.objects.create_user(bundle.data['username'], bundle.data['email'], bundle.data['password'])
+            user.first_name = bundle.data.get('first_name', '')
+            user.last_name = bundle.data.get('last_name', '')
+
+            bundle.obj = user
+        
+        bundle.data['groups'] = bundle.data.get('groups', [])
+
+        if bundle.obj.pk:
+            self.authorized_update_detail(self.get_object_list(bundle.request), bundle)
+        else:
+            self.authorized_create_detail(self.get_object_list(bundle.request), bundle)
+
+        self.save_related(bundle)
+
+        obj_id = self.create_identifier(bundle.obj)
+
+        if obj_id not in bundle.objects_saved or bundle.obj._state.adding:
+            bundle.obj.save()
+            bundle.objects_saved.add(obj_id)
+
+        m2m_bundle = self.hydrate_m2m(bundle)
+        self.save_m2m(m2m_bundle)
+        
+        return bundle

+ 42 - 0
eiru-automation/app/api/templates/openerp-server.j2

@@ -0,0 +1,42 @@
+[options]
+addons_path = /mnt/extra-addons,/usr/lib/python2.7/dist-packages/openerp/addons
+data_dir = /var/lib/odoo
+; auto_reload = True
+admin_passwd = {{ admin_password }}
+; csv_internal_sep = ,
+; db_maxconn = 64
+db_host = {{ db_host }}
+db_port = {{ db_port }}
+db_name = {{ db_name }}
+db_user = {{ db_user }}
+db_password = {{ db_password }}
+; db_template = template1
+dbfilter = ^{{ db_name }}$
+; debug_mode = False
+; email_from = False
+; limit_memory_hard = 2684354560
+; limit_memory_soft = 2147483648
+; limit_request = 8192
+; limit_time_cpu = 240
+; limit_time_real = 840
+list_db = False
+; log_db = False
+; log_handler = [':INFO']
+; log_level = debug_sql
+; logfile = None
+; longpolling_port = 8072
+; max_cron_threads = 2
+; osv_memory_age_limit = 1.0
+; osv_memory_count_limit = False
+; smtp_password = False
+; smtp_port = 25
+; smtp_server = localhost
+; smtp_ssl = False
+; smtp_user = False
+; workers = 7
+; xmlrpc = True
+; xmlrpc_interface =
+; xmlrpc_port = 8069
+; xmlrpcs = True
+; xmlrpcs_interface =
+; xmlrpcs_port = 8071

+ 23 - 0
eiru-automation/app/api/urls.py

@@ -0,0 +1,23 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+from django.conf.urls import url
+from tastypie.api import Api
+
+from api.resources.user_resource import UserResource
+from api.resources.permission_resource import PermissionResource
+from api.resources.group_resource import GroupResource
+from api.resources.jwt_resource import JWTResource
+from api.resources.request_resource import RequestResource
+from api.resources.task_resource import TaskResource
+from api.resources.docker_resource import DockerResource
+from api.resources.odoo_resource import OdooResource
+
+v1_api = Api(api_name='v1')
+v1_api.register(UserResource())
+v1_api.register(PermissionResource())
+v1_api.register(GroupResource())
+v1_api.register(JWTResource())
+v1_api.register(RequestResource())
+v1_api.register(TaskResource())
+v1_api.register(DockerResource())
+v1_api.register(OdooResource())

+ 0 - 0
eiru-automation/app/api/utils/__init__.py


+ 28 - 0
eiru-automation/app/api/utils/command.py

@@ -0,0 +1,28 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+from subprocess import check_output, STDOUT
+import subprocess
+
+'''
+'''
+def list_files_and_folders(path, hiddens=True):
+    output = check_output(['ls', '-a', path], stderr=STDOUT)
+
+    items = []
+    for item in output.split('\n'):
+        if item == '' or item == '.' or item == '..':
+            continue
+        
+        if not hiddens and item.startswith('.'):
+            continue
+
+        items.append(item)
+
+    return {
+        'items': items
+    }
+
+'''
+'''
+def execute(command=[]):
+    return check_output(command)

+ 201 - 0
eiru-automation/app/api/utils/docker_api.py

@@ -0,0 +1,201 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+from django.conf import settings
+from docker import DockerClient
+from docker.errors import DockerException, NotFound, APIError, ContainerError, ImageNotFound
+import time
+
+'''
+'''
+def get_docker_client():
+    try:
+        return DockerClient(base_url='unix:/%s' % settings.DOCKER_SOCK_DIR)
+    except DockerException:
+        return None
+
+'''
+'''
+def get_all_images():
+    client = get_docker_client()
+
+    images = []
+
+    if not client:
+        return images
+
+    for image in client.images.list():
+        images.append({
+            'id': image.id,
+            'short_id': image.short_id,
+            'tags': image.tags
+        })
+
+    return images
+
+'''
+'''
+def get_all_containers():
+    client = get_docker_client()
+
+    containers = []
+
+    if not client:
+        return containers
+
+    for container in client.containers.list(all=True):
+        containers.append({
+            'id': container.id,
+            'short_id': container.short_id,
+            'name': container.name,
+            'image': container.image,
+            'labels': container.labels,
+            'status': container.status,
+            'attrs': container.attrs
+        })
+        
+    return containers
+
+'''
+'''
+def run_container(image=None, name=None, ports=[], volumes=None, net=None):
+    if not name:
+        return False
+
+    client = get_docker_client()
+
+    if not client:
+        return False
+
+    try:
+        client.containers.run(image, None, name=name, detach=True, ports=ports, volumes=volumes, network=net)
+        return True
+    except (ContainerError, ImageNotFound, APIError):
+        return False
+
+'''
+'''
+def start_container(id=None):
+    if not id:
+        return None
+
+    client = get_docker_client()
+
+    if not client:
+        return None
+
+    try:
+        container = client.containers.get(id)
+        container.start()
+
+        time.sleep(1)
+
+        container.reload()
+
+        return {
+            'id': container.id,
+            'short_id': container.short_id,
+            'name': container.name,
+            'image': container.image,
+            'labels': container.labels,
+            'status': container.status
+        }
+    except (NotFound, APIError):
+        return None
+
+'''
+'''
+def restart_container(id=None):
+    if not id:
+        return None
+
+    client = get_docker_client()
+
+    if not client:
+        return None
+
+    try:
+        container = client.containers.get(id)
+        container.restart()
+
+        time.sleep(1)
+
+        container.reload()
+
+        return {
+            'id': container.id,
+            'short_id': container.short_id,
+            'name': container.name,
+            'image': container.image,
+            'labels': container.labels,
+            'status': container.status
+        }
+    except (NotFound, APIError):
+        return None
+
+'''
+'''
+def stop_container(id=None):
+    if not id:
+        return None
+
+    client = get_docker_client()
+
+    if not client:
+        return None
+
+    try:
+        container = client.containers.get(id)
+        container.stop()
+
+        time.sleep(1)
+
+        container.reload()
+
+        return {
+            'id': container.id,
+            'short_id': container.short_id,
+            'name': container.name,
+            'image': container.image,
+            'labels': container.labels,
+            'status': container.status
+        }
+    except (NotFound, APIError):
+        return None
+
+'''
+'''
+def execute_command(container_id_or_name=None, command=None):
+    if not container_id_or_name or not command:
+        return None
+
+    client = get_docker_client()
+
+    if not client:
+        return None
+
+    try:
+        container = client.containers.get(container_id_or_name)
+        (exit_code, unused_output) = container.exec_run(command)
+        return (False, True)[exit_code == 0]
+    except (NotFound, APIError):
+        return None
+
+'''
+'''
+def copy_in(container_id_or_name=None, path=None, tarfile=None):
+    if not container_id_or_name or not path or not tarfile:
+        return False
+
+    client = get_docker_client()
+
+    if not client:
+        return False
+
+    try:
+        file = open(tarfile, 'r')
+        container = client.containers.get(container_id_or_name)
+        result = container.put_archive(path, file)
+        file.close()
+        return result
+    except (NotFound, APIError, IOError):
+        return False

+ 52 - 0
eiru-automation/app/api/utils/jwt_authentication.py

@@ -0,0 +1,52 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+from tastypie.authentication import Authentication
+from django.contrib.auth.models import User
+from django.conf import settings
+from api.utils.jwt_token import check_token, get_username
+import simplejson as json
+
+class JWTAuthentication(Authentication):
+    
+    '''
+    '''
+    def is_authenticated(self, request, **kwargs):
+        # # Check content type
+        # if request.content_type != 'application/json':
+        #     return False
+
+        # Check authorization header
+        if settings.JWT_ACCEPT_HEADER not in request.META:
+            return False
+
+        authorization_header = request.META.get(settings.JWT_ACCEPT_HEADER)
+
+        # Check authorization header prefix
+        if not authorization_header.startswith(settings.JWT_PREFIX_HEADER):
+            return False
+
+        prefix_length = len(settings.JWT_PREFIX_HEADER)
+        (_, ok) = check_token(authorization_header[prefix_length + 1:])
+
+        return ok
+    '''
+    '''
+    def get_identifier(self, request):
+        # Check content type
+        if request.content_type != 'application/json':
+
+            return False
+
+        # Check authorization header
+        if settings.JWT_ACCEPT_HEADER not in request.META:
+            return False
+
+        authorization_header = request.META.get(settings.JWT_ACCEPT_HEADER)
+
+        # Check authorization header prefix
+        if not authorization_header.startswith(settings.JWT_PREFIX_HEADER):
+            return False
+
+        prefix_length = len(settings.JWT_PREFIX_HEADER)
+
+        return get_username(authorization_header[prefix_length + 1:])

+ 93 - 0
eiru-automation/app/api/utils/jwt_token.py

@@ -0,0 +1,93 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.conf import settings
+from django.contrib.auth import authenticate
+from django.contrib.auth.models import User
+from django.utils.crypto import constant_time_compare
+from jwt import DecodeError
+import jwt
+
+'''
+'''
+def create_token(username, password):
+    # Check if exists jwt key
+    if not settings.JWT_SECRET_KEY:
+        return None
+
+    user = authenticate(username=username, password=password)
+
+    # Check user authentication
+    if not user:
+        return user
+
+    payload = {
+        'uid': user.id,
+        'password': user.password
+    }
+
+    return jwt.encode(payload, settings.JWT_SECRET_KEY, algorithm='HS256')
+
+'''
+'''
+def explode_token(token):
+    if not token:
+        return False
+
+    # Normalize token
+    if token.startswith(settings.JWT_PREFIX_HEADER):
+        prefix_length = len(settings.JWT_PREFIX_HEADER) 
+        token = token[prefix_length + 1:]
+
+    # Check if exists jwt key
+    if not settings.JWT_SECRET_KEY:
+        return None
+
+    payload = None
+
+    try:
+        payload = jwt.decode(token, settings.JWT_SECRET_KEY, algorithm='HS256')
+    except DecodeError:
+        return False
+
+    # Check payload parameters
+    if 'uid' not in payload or 'password' not in payload:
+        return False
+    
+    return payload
+
+'''
+'''
+def get_user(token):
+    payload = explode_token(token)
+
+    user = User.objects.get(pk=payload['uid'])
+
+    return user
+
+'''
+'''
+def get_username(token):
+    user = get_user(token)
+
+    # Check if exists user
+    if not user:
+        return user
+
+    return user.name
+
+'''
+'''
+def check_token(token):
+    payload = explode_token(token)
+     
+    if not payload:
+        return (None, False)
+
+    user = User.objects.get(pk=payload['uid'])
+
+    # Check if exists user
+    if not user:
+        return (None, False)
+
+    return (user, constant_time_compare(user.password, payload['password']))

+ 177 - 0
eiru-automation/app/api/utils/odoo_api.py

@@ -0,0 +1,177 @@
+from __future__ import unicode_literals
+from django.conf import settings
+from random import randint
+from jinja2 import Environment, PackageLoader, select_autoescape
+from api.utils.docker_api import (
+    execute_command,
+    run_container,
+    copy_in
+)
+from api.utils.command import execute
+import os
+import socket
+import time
+import unicodedata
+import stringcase
+import tarfile
+
+'''
+'''
+def normalize_name(name = None):
+    if not name:
+        return name
+    
+    try:
+        name = unicodedata.normalize('NFKD', name)
+        name = name.encode('ASCII', 'ignore') 
+    except TypeError:
+        pass
+
+    name = stringcase.trimcase(name)
+    name = stringcase.lowercase(name)
+    name = stringcase.snakecase(name)
+
+    return name
+
+'''
+'''
+def randomize_port():
+    ports = settings.ODOO_PORTS_RANGE
+    port = 0
+
+    while not check_port(port):
+        time.sleep(1)
+        port = randint(ports[0], ports[1])
+
+    return port
+    
+'''
+'''
+def check_port(port=0):
+    if port == 0:
+        return False
+
+    ok = False
+    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+
+    try:
+        s.bind((settings.DOCKER_NETWORK, port))
+        ok = True
+    except socket.error:
+        ok = False
+    
+    s.close()
+    return ok
+
+'''
+'''
+def check_name(name=None):
+    if not name:
+        return False
+
+    full_path = os.path.join(settings.ODOO_ROOT_PATH, name)
+    return os.path.exists(full_path)
+
+'''
+'''
+def create_folders(name=None):
+    if not name:
+        return False
+
+    folders = settings.ODOO_DEFAULT_FOLDERS
+    for folder in folders:
+        full_path = os.path.join(settings.ODOO_ROOT_PATH, name, folder.strip())
+        os.makedirs(full_path)
+        time.sleep(1)
+
+    return True
+
+'''
+'''
+def apply_permissions(name=None):
+    if not name:
+        return False
+    
+    full_path = os.path.join(settings.ODOO_ROOT_PATH, name)
+    execute(['chmod', '-Rf', '777', full_path])
+
+    return True
+
+'''
+'''
+def create_configuration(name=None):
+    if not name:
+        return False
+
+    conf_path = os.path.join(settings.ODOO_ROOT_PATH, name, 'config')
+
+    if not os.path.exists(conf_path):
+        return False
+
+    env = Environment(
+        loader=PackageLoader('api', 'templates'),
+        autoescape=select_autoescape(['j2'])
+    )
+
+    template = env.get_template(settings.ODOO_CONF_FILENAME + '.j2')
+
+    template_rendered = template.stream({
+        'admin_password': settings.ODOO_ADMIN_PASSWORD, 
+        'db_host': settings.ODOO_DB_HOST,
+        'db_port': settings.ODOO_DB_PORT,
+        'db_name': name, 
+        'db_user': settings.ODOO_DB_USER, 
+        'db_password': settings.ODOO_DB_PASSWORD
+    })
+
+    template_rendered.dump(os.path.join(conf_path, settings.ODOO_CONF_FILENAME + '.conf'))
+
+    return True
+
+'''
+'''
+def create_database(name=None):
+    if not name:
+        return False
+
+    cmd = 'createdb -U %s %s' % (settings.ODOO_DB_USER, name)
+    return execute_command(settings.ODOO_DB_CONTAINER, cmd)
+
+'''
+'''
+def copy_database_seed():
+    backup_path = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'files', 'odoo.tar')
+    return copy_in(settings.ODOO_DB_CONTAINER, '/tmp', backup_path)
+
+'''
+'''
+def restore_database(name=None):
+    if not name:
+        return False
+
+    cmd = 'psql -U %s -d %s -f %s' % (settings.ODOO_DB_USER, name, '/tmp/odoo.sql')
+    return execute_command(settings.ODOO_DB_CONTAINER, cmd)
+
+'''
+'''
+def remove_database_seed():
+    cmd = 'rm -f %s' % ('/tmp/odoo.sql')
+    return execute_command(settings.ODOO_DB_CONTAINER, cmd)
+
+'''
+'''
+def create_odoo_container(name=None, ports=[]):
+    if not name:
+        return False
+
+    ports = dict(map(lambda p: ('%d/tcp' % 8069, p), ports))
+    volumes = dict(map(lambda f: (os.path.join(settings.ODOO_ROOT_PATH, name, f.strip()), {
+        'bind': settings.ODOO_DEFAULT_FOLDERS[f].strip(),
+        'mode': 'rw'
+    })  , settings.ODOO_DEFAULT_FOLDERS))
+
+    run_container(settings.ODOO_IMAGE, name, ports, volumes, settings.DOCKER_NETWORK_NAME)
+    
+    time.sleep(5)
+
+    return True

+ 0 - 0
eiru-automation/app/api/validations/__init__.py


+ 45 - 0
eiru-automation/app/api/validations/user_validation.py

@@ -0,0 +1,45 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+from tastypie.validation import Validation
+
+class UserValidation(Validation):
+    
+    '''
+    '''
+    def is_valid(self, bundle, request=None):
+        if not bundle.data:
+            return {
+                '__all__': 'data not provided in bundle'
+            }
+
+        errors = {}
+
+        if request.method == 'POST':
+            if not 'first_name' in bundle.data:
+                errors['first_name'] = 'this field is required'
+
+            if not 'last_name' in bundle.data:
+                errors['last_name'] = 'this field is required'
+
+            if not 'username' in bundle.data:
+                errors['username'] = 'this field is required'
+
+            if not 'email' in bundle.data:
+                errors['email'] = 'this field is required'
+
+            if not 'password' in bundle.data:
+                errors['password'] = 'this field is required'
+        else:
+            if 'username' in bundle.data:
+                errors['username'] = 'cannot update this field'
+
+            if 'password' in bundle.data:
+                errors['password'] = 'cannot update password here, use endpoint for specific action'
+
+        if 'last_login' in bundle.data:
+            errors['last_login'] = 'cannot create or update this field'
+
+        if 'date_joined' in bundle.data:
+            errors['date_joined'] = 'cannot create or update this field'
+
+        return errors

+ 0 - 0
eiru-automation/app/core/__init__.py


+ 8 - 0
eiru-automation/app/core/apps.py

@@ -0,0 +1,8 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.apps import AppConfig
+
+
+class CoreConfig(AppConfig):
+    name = 'core'

+ 48 - 0
eiru-automation/app/core/migrations/0001_initial.py

@@ -0,0 +1,48 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11 on 2018-03-28 19:06
+from __future__ import unicode_literals
+
+from django.conf import settings
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    initial = True
+
+    dependencies = [
+        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='Request',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('name', models.CharField(max_length=35)),
+                ('create_at', models.DateTimeField(auto_now_add=True)),
+                ('update_at', models.DateTimeField(auto_now=True)),
+                ('status', models.PositiveSmallIntegerField(choices=[(1, 'Abierto'), (2, 'Rechazado'), (3, 'Procesando'), (4, 'Hecho'), (5, 'Error')], default=1)),
+                ('issue', models.CharField(blank=True, max_length=100)),
+                ('user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
+            ],
+            options={
+                'abstract': False,
+            },
+        ),
+        migrations.CreateModel(
+            name='Task',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('name', models.CharField(max_length=35)),
+                ('create_at', models.DateTimeField(auto_now_add=True)),
+                ('update_at', models.DateTimeField(auto_now=True)),
+                ('last_execution', models.DateTimeField()),
+                ('request', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='core.Request')),
+            ],
+            options={
+                'abstract': False,
+            },
+        ),
+    ]

+ 0 - 0
eiru-automation/app/core/migrations/__init__.py


+ 4 - 0
eiru-automation/app/core/models/__init__.py

@@ -0,0 +1,4 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+from .request import Request
+from .task import Task

+ 13 - 0
eiru-automation/app/core/models/base.py

@@ -0,0 +1,13 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+from django.db import models
+
+'''
+'''
+class Base(models.Model):
+    name = models.CharField(max_length=35, blank=False)
+    create_at = models.DateTimeField(auto_now_add=True)
+    update_at = models.DateTimeField(auto_now=True)
+
+    class Meta:
+        abstract = True

+ 20 - 0
eiru-automation/app/core/models/request.py

@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+from django.db import models
+from django.contrib.auth.models import User
+from core.models.base import Base
+
+REQUEST_STATUSES = (
+    (1, 'Abierto'),
+    (2, 'Rechazado'),
+    (3, 'Procesando'),
+    (4, 'Hecho'),
+    (5, 'Error'),
+)
+
+'''
+'''
+class Request(Base):
+    status = models.PositiveSmallIntegerField(choices=REQUEST_STATUSES, default=1)
+    issue = models.CharField(max_length=100, blank=True)
+    user = models.ForeignKey(User, on_delete=models.CASCADE, null=True)

+ 11 - 0
eiru-automation/app/core/models/task.py

@@ -0,0 +1,11 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+from django.db import models
+from core.models.base import Base
+from core.models.request import Request
+
+'''
+'''
+class Task(Base):
+    last_execution = models.DateTimeField()
+    request = models.ForeignKey(Request, on_delete=models.CASCADE, null=True)

+ 22 - 0
eiru-automation/app/manage.py

@@ -0,0 +1,22 @@
+#!/usr/bin/env python
+import os
+import sys
+
+if __name__ == "__main__":
+    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "odoo_control.settings")
+    try:
+        from django.core.management import execute_from_command_line
+    except ImportError:
+        # The above import may fail for some other reason. Ensure that the
+        # issue is really that Django is missing to avoid masking other
+        # exceptions on Python 2.
+        try:
+            import django
+        except ImportError:
+            raise ImportError(
+                "Couldn't import Django. Are you sure it's installed and "
+                "available on your PYTHONPATH environment variable? Did you "
+                "forget to activate a virtual environment?"
+            )
+        raise
+    execute_from_command_line(sys.argv)

+ 0 - 0
eiru-automation/app/odoo_control/__init__.py


+ 102 - 0
eiru-automation/app/odoo_control/settings.py

@@ -0,0 +1,102 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+from decouple import config, Csv
+from corsheaders.defaults import default_headers
+import os
+
+BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
+INSTALLED_APPS = [
+    'django.contrib.admin',
+    'django.contrib.auth',
+    'django.contrib.contenttypes',
+    'django.contrib.sessions',
+    'django.contrib.messages',
+    'django.contrib.staticfiles',
+    'django_extensions',
+    'tastypie',
+    'core.apps.CoreConfig',
+    'api.apps.ApiConfig',
+    'corsheaders',
+]
+MIDDLEWARE = [
+    'corsheaders.middleware.CorsMiddleware',
+    'django.middleware.security.SecurityMiddleware',
+    'django.contrib.sessions.middleware.SessionMiddleware',
+    'django.middleware.common.CommonMiddleware',
+    'django.middleware.csrf.CsrfViewMiddleware',
+    'django.contrib.auth.middleware.AuthenticationMiddleware',
+    'django.contrib.messages.middleware.MessageMiddleware',
+    'django.middleware.clickjacking.XFrameOptionsMiddleware',
+]
+ROOT_URLCONF = 'odoo_control.urls'
+TEMPLATES = [
+    {
+        'BACKEND': 'django.template.backends.django.DjangoTemplates',
+        'DIRS': [],
+        'APP_DIRS': True,
+        'OPTIONS': {
+            'context_processors': [
+                'django.template.context_processors.debug',
+                'django.template.context_processors.request',
+                'django.contrib.auth.context_processors.auth',
+                'django.contrib.messages.context_processors.messages',
+            ],
+        },
+    },
+]
+WSGI_APPLICATION = 'odoo_control.wsgi.application'
+DATABASES = {
+    'default': {
+        'ENGINE': 'django.db.backends.sqlite3',
+        'NAME': os.path.join(BASE_DIR, 'app.db'),
+    }
+}
+AUTH_PASSWORD_VALIDATORS = [
+    {
+        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
+    },
+    {
+        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
+    },
+    {
+        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
+    },
+    {
+        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
+    },
+]
+# AUTH_USER_MODEL = 'core.User'
+LANGUAGE_CODE = 'es-PY'
+TIME_ZONE = 'UTC'
+USE_I18N = True
+USE_L10N = True
+USE_TZ = True
+STATIC_URL = '/static/'
+TASTYPIE_DEFAULT_FORMATS = ['json', 'xml']
+SECRET_KEY = config('SECRET_KEY')
+ALLOWED_HOSTS = config('ALLOWED_HOSTS', cast=Csv())
+CORS_ORIGIN_ALLOW_ALL = True
+# CORS_ALLOW_HEADERS = default_headers + (
+#     'withCredentials',
+#     'credentials'
+# )
+CORS_ALLOW_CREDENTIALS = True
+DEBUG = config('DEBUG', default=False, cast=bool)
+JWT_ACCEPT_HEADER = config('JWT_ACCEPT_HEADER')
+JWT_PREFIX_HEADER = config('JWT_PREFIX_HEADER')
+JWT_SECRET_KEY = config('JWT_SECRET_KEY')
+SERVER_IP = config('SERVER_IP')
+DOCKER_SOCK_DIR = config('DOCKER_SOCK_DIR')
+DOCKER_NETWORK = config('DOCKER_NETWORK')
+DOCKER_NETWORK_NAME = config('DOCKER_NETWORK_NAME', 'default')
+ODOO_IMAGE = config('ODOO_IMAGE')
+ODOO_ROOT_PATH = config('ODOO_ROOT_PATH')
+ODOO_DEFAULT_FOLDERS = config('ODOO_DEFAULT_FOLDERS', cast=lambda x: dict([s.split(':') for s in x.split(',')]))
+ODOO_PORTS_RANGE = config('ODOO_PORTS_RANGE', cast=Csv(int))
+ODOO_CONF_FILENAME = config('ODOO_CONF_FILENAME', 'openerp-server')
+ODOO_ADMIN_PASSWORD = config('ODOO_ADMIN_PASSWORD', 'admin')
+ODOO_DB_CONTAINER = config('ODOO_DB_CONTAINER', 'postgres')
+ODOO_DB_HOST = config('ODOO_DB_HOST', 'localhost')
+ODOO_DB_PORT = config('ODOO_DB_PORT', default=5432, cast=int)
+ODOO_DB_USER = config('ODOO_DB_USER', 'postgres')
+ODOO_DB_PASSWORD = config('ODOO_DB_PASSWORD', 'root')

+ 13 - 0
eiru-automation/app/odoo_control/urls.py

@@ -0,0 +1,13 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+from django.conf.urls import url, include
+from django.contrib import admin
+
+from api.urls import v1_api
+from ui.views import index
+
+urlpatterns = [
+    url(r'^admin/', admin.site.urls),
+    url(r'^api/', include(v1_api.urls)),
+    url(r'^ui/', index)
+]

+ 16 - 0
eiru-automation/app/odoo_control/wsgi.py

@@ -0,0 +1,16 @@
+"""
+WSGI config for odoo_control project.
+
+It exposes the WSGI callable as a module-level variable named ``application``.
+
+For more information on this file, see
+https://docs.djangoproject.com/en/1.11/howto/deployment/wsgi/
+"""
+
+import os
+
+from django.core.wsgi import get_wsgi_application
+
+os.environ.setdefault("DJANGO_SETTINGS_MODULE", "odoo_control.settings")
+
+application = get_wsgi_application()

+ 38 - 0
eiru-automation/app/requirements.txt

@@ -0,0 +1,38 @@
+astroid==1.6.1
+backports.functools-lru-cache==1.5
+backports.ssl-match-hostname==3.5.0.1
+certifi==2018.1.18
+chardet==3.0.4
+configparser==3.5.0
+Django==1.11
+django-cors-headers==2.2.0
+django-extensions==2.0.0
+django-tastypie==0.14.0
+docker==3.0.1
+docker-pycreds==0.2.1
+enum34==1.1.6
+futures==3.2.0
+idna==2.6
+ipaddress==1.0.19
+isort==4.3.4
+Jinja2==2.10
+lazy-object-proxy==1.3.1
+MarkupSafe==1.0
+mccabe==0.6.1
+pkg-resources==0.0.0
+PyJWT==1.5.3
+pylint==1.8.2
+python-dateutil==2.6.1
+python-decouple==3.1
+python-mimeparse==1.6.0
+pytz==2018.3
+requests==2.18.4
+simplejson==3.13.2
+singledispatch==3.4.0.3
+six==1.11.0
+stringcase==1.2.0
+typing==3.6.4
+urllib3==1.22
+websocket-client==0.46.0
+Werkzeug==0.14.1
+wrapt==1.10.11

+ 0 - 0
eiru-automation/app/ui/__init__.py


+ 6 - 0
eiru-automation/app/ui/admin.py

@@ -0,0 +1,6 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.contrib import admin
+
+# Register your models here.

+ 8 - 0
eiru-automation/app/ui/apps.py

@@ -0,0 +1,8 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.apps import AppConfig
+
+
+class UiConfig(AppConfig):
+    name = 'ui'

+ 0 - 0
eiru-automation/app/ui/migrations/__init__.py


+ 9 - 0
eiru-automation/app/ui/package.json

@@ -0,0 +1,9 @@
+{
+  "name": "eiru_automation_ui",
+  "version": "1.0.0",
+  "description": "Eiru Automation UI",
+  "main": "index.js",
+  "author": "Robert Gauto",
+  "license": "MIT",
+  "private": true
+}

+ 15 - 0
eiru-automation/app/ui/templates/index.html

@@ -0,0 +1,15 @@
+{% load static %}
+<!DOCTYPE html>
+<html lang="en">
+    <head>
+        <meta charset="UTF-8">
+        <meta name="viewport" content="width=device-width, initial-scale=1.0">
+        <meta http-equiv="X-UA-Compatible" content="ie=edge">
+        <title>Eiru Automation</title>
+        <link rel="stylesheet" href="{% static 'app.css' %}">
+    </head>
+    <body>
+        <div id="root"></div>
+        <script src="{% static 'app.js' %}"></script>
+    </body>
+</html>

+ 8 - 0
eiru-automation/app/ui/views.py

@@ -0,0 +1,8 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+from django.shortcuts import render
+
+'''
+'''
+def index(request):
+    return render(request, 'index.html')

+ 4 - 0
eiru-automation/app/yarn.lock

@@ -0,0 +1,4 @@
+# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
+# yarn lockfile v1
+
+