From ad2f65a970b8625d6c3174bba4202d038c44b67d Mon Sep 17 00:00:00 2001 From: Sergey Klyuykov Date: Wed, 20 Dec 2017 09:41:14 +1000 Subject: [PATCH 01/14] Add badge for docs [skip ci] --- README.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.rst b/README.rst index cd4aa9ae..a58d6d22 100644 --- a/README.rst +++ b/README.rst @@ -1,6 +1,10 @@ Polemarch ========= +.. image:: https://readthedocs.org/projects/polemarch/badge/?version=stable + :target: http://polemarch.readthedocs.io/en/stable/?badge=stable + :alt: Documentation Status + **Polemarch** is service for orchestration infrastructure by ansible. Simply WEB gui for orchestration infrastructure by ansible playbooks. From 05e5809d85274d6a7edeb8fa5cb424fce785961d Mon Sep 17 00:00:00 2001 From: cepreu Date: Wed, 20 Dec 2017 15:05:55 +1000 Subject: [PATCH 02/14] EXECUTOR access to POST methods like execute_module (issue polemarchplus#99) --- polemarch/api/base.py | 2 +- polemarch/api/permissions.py | 8 +++++--- polemarch/api/v1/views.py | 4 ++++ polemarch/main/models/tasks.py | 10 ++++++++-- 4 files changed, 18 insertions(+), 6 deletions(-) diff --git a/polemarch/api/base.py b/polemarch/api/base.py index 0bcb4aa6..9e2498dd 100644 --- a/polemarch/api/base.py +++ b/polemarch/api/base.py @@ -140,7 +140,7 @@ class HistoryModelViewSet(GenericViewSet, class ModelViewSetSet(GenericViewSet, viewsets.ModelViewSet): - pass + POST_WHITE_LIST = [] class NonModelsViewSet(GenericViewSet): diff --git a/polemarch/api/permissions.py b/polemarch/api/permissions.py index a6e4e780..8d09bfa9 100644 --- a/polemarch/api/permissions.py +++ b/polemarch/api/permissions.py @@ -10,9 +10,11 @@ def has_object_permission(self, request, view, obj): return True if request.user == obj: # nocv return True - if request.method not in permissions.SAFE_METHODS: # nocv - return obj.editable_by(request.user) - return obj.viewable_by(request.user) # nocv + if request.method in permissions.SAFE_METHODS: # nocv + return obj.viewable_by(request.user) # nocv + if view.action in view.POST_WHITE_LIST: # nocv + return obj.viewable_by(request.user) # nocv + return obj.editable_by(request.user) class SuperUserPermission(ModelPermission): diff --git a/polemarch/api/v1/views.py b/polemarch/api/v1/views.py index fbf6250e..237dbdf2 100644 --- a/polemarch/api/v1/views.py +++ b/polemarch/api/v1/views.py @@ -109,6 +109,7 @@ class ProjectViewSet(base.PermissionMixin, base.ModelViewSetSet, serializer_class = serializers.ProjectSerializer serializer_class_one = serializers.OneProjectSerializer filter_class = filters.ProjectFilter + POST_WHITE_LIST = ['sync', 'execute_playbook', 'execute_module'] @list_route(methods=["get"], url_path="supported-repos") def supported_repos(self, request): @@ -146,6 +147,7 @@ class PeriodicTaskViewSet(base.LimitedPermissionMixin, base.ModelViewSetSet): serializer_class = serializers.PeriodictaskSerializer serializer_class_one = serializers.OnePeriodictaskSerializer filter_class = filters.PeriodicTaskFilter + POST_WHITE_LIST = ['execute'] @detail_route(methods=["post"]) def execute(self, request, *args, **kwargs): @@ -158,6 +160,7 @@ class HistoryViewSet(base.LimitedPermissionMixin, base.HistoryModelViewSet): serializer_class = serializers.HistorySerializer serializer_class_one = serializers.OneHistorySerializer filter_class = filters.HistoryFilter + POST_WHITE_LIST = ['cancel'] @detail_route(methods=["get"]) def raw(self, request, *args, **kwargs): @@ -190,6 +193,7 @@ class TemplateViewSet(base.PermissionMixin, base.ModelViewSetSet): serializer_class = serializers.TemplateSerializer serializer_class_one = serializers.OneTemplateSerializer filter_class = filters.TemplateFilter + POST_WHITE_LIST = ['execute'] @list_route(methods=["get"], url_path="supported-kinds") def supported_kinds(self, request): diff --git a/polemarch/main/models/tasks.py b/polemarch/main/models/tasks.py index b14c4594..1433990e 100644 --- a/polemarch/main/models/tasks.py +++ b/polemarch/main/models/tasks.py @@ -422,12 +422,18 @@ def editable_by(self, user): return self.project.editable_by(user) return self.inventory.editable_by(user) + def _inventory_editable(self, user): + return self.inventory and self.inventory.editable_by(user) + + def _inventory_viewable(self, user): + return not self.inventory or self.inventory.viewable_by(user) + def viewable_by(self, user): return ( self.project.editable_by(user) or - self.inventory.editable_by(user) or + self._inventory_editable(user) or (self.initiator == user.id and self.initiator_type == "users") or - (self.project.viewable_by(user) & self.inventory.viewable_by(user)) + (self.project.viewable_by(user) & self._inventory_viewable(user)) ) From 5f967e38f4173fbbc273ed5ff13dff11b4716277 Mon Sep 17 00:00:00 2001 From: cepreu Date: Wed, 20 Dec 2017 15:14:08 +1000 Subject: [PATCH 03/14] aargh! spaces! --- polemarch/api/permissions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/polemarch/api/permissions.py b/polemarch/api/permissions.py index 8d09bfa9..dd2fd6bf 100644 --- a/polemarch/api/permissions.py +++ b/polemarch/api/permissions.py @@ -12,7 +12,7 @@ def has_object_permission(self, request, view, obj): return True if request.method in permissions.SAFE_METHODS: # nocv return obj.viewable_by(request.user) # nocv - if view.action in view.POST_WHITE_LIST: # nocv + if view.action in view.POST_WHITE_LIST: # nocv return obj.viewable_by(request.user) # nocv return obj.editable_by(request.user) From 888d14559e9b7ec6bf7c6ddbd00b0ace224d2c68 Mon Sep 17 00:00:00 2001 From: cepreu Date: Wed, 20 Dec 2017 17:37:19 +1000 Subject: [PATCH 04/14] teams visible for all (issue polemarchplus#101) --- polemarch/api/v1/views.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/polemarch/api/v1/views.py b/polemarch/api/v1/views.py index 237dbdf2..849787c7 100644 --- a/polemarch/api/v1/views.py +++ b/polemarch/api/v1/views.py @@ -62,6 +62,9 @@ class TeamViewSet(base.PermissionMixin, base.ModelViewSetSet): serializer_class_one = serializers.OneTeamSerializer filter_class = filters.TeamFilter + def get_extra_queryset(self): + return self.queryset + class HostViewSet(base.PermissionMixin, base.ModelViewSetSet): model = serializers.models.Host From 2998da1d8b9a4821ed955036d8bb21f67a341de9 Mon Sep 17 00:00:00 2001 From: Sergey Klyuykov Date: Wed, 20 Dec 2017 18:32:05 +1000 Subject: [PATCH 05/14] Add new hooks types New types: * on_user_add * on_user_upd * on_user_del * on_object_add * on_object_upd * on_object_del cloud/polemarchplus#8 --- polemarch/api/v1/serializers.py | 1 + polemarch/api/v1/views.py | 4 +-- polemarch/main/hooks/base.py | 18 ++++++++++ polemarch/main/models/__init__.py | 60 +++++++++++++++++++++++++++++-- polemarch/main/models/hooks.py | 12 ++++++- polemarch/main/tests/api.py | 55 +++++++++++++++++++++++----- polemarch/main/tests/tasks.py | 24 ++++++++++++- 7 files changed, 159 insertions(+), 15 deletions(-) diff --git a/polemarch/api/v1/serializers.py b/polemarch/api/v1/serializers.py index cf0aa87e..4c53f309 100644 --- a/polemarch/api/v1/serializers.py +++ b/polemarch/api/v1/serializers.py @@ -336,6 +336,7 @@ class Meta: 'id', 'name', 'type', + 'when', 'recipients' ) diff --git a/polemarch/api/v1/views.py b/polemarch/api/v1/views.py index fbf6250e..eee13e8f 100644 --- a/polemarch/api/v1/views.py +++ b/polemarch/api/v1/views.py @@ -210,8 +210,8 @@ class HookViewSet(base.ModelViewSetSet): @list_route(['get']) def types(self, request): data = dict( - types=self.model.handlers.list(), - when=self.model.handlers.when_types + types=self.model.handlers.list().keys(), + when=self.model.handlers.when_types_names ) return base.Response(data, 200).resp diff --git a/polemarch/main/hooks/base.py b/polemarch/main/hooks/base.py index 459525ac..9f8a03a4 100644 --- a/polemarch/main/hooks/base.py +++ b/polemarch/main/hooks/base.py @@ -33,3 +33,21 @@ def on_execution(self, message): def after_execution(self, message): return self.send(message, when='after_execution') + + def on_user_add(self, message): + return self.send(message, when='on_user_add') + + def on_user_upd(self, message): + return self.send(message, when='on_user_upd') + + def on_user_del(self, message): + return self.send(message, when='on_user_del') + + def on_object_add(self, message): + return self.send(message, when='on_object_add') + + def on_object_upd(self, message): + return self.send(message, when='on_object_upd') + + def on_object_del(self, message): + return self.send(message, when='on_object_del') diff --git a/polemarch/main/models/__init__.py b/polemarch/main/models/__init__.py index db58d10f..8136c9da 100644 --- a/polemarch/main/models/__init__.py +++ b/polemarch/main/models/__init__.py @@ -2,7 +2,7 @@ from __future__ import absolute_import import os import json - +from collections import OrderedDict import django_celery_beat from django_celery_beat.models import IntervalSchedule, CrontabSchedule from django.db.models import signals @@ -13,12 +13,39 @@ from .vars import Variable from .hosts import Host, Group, Inventory from .projects import Project -from .users import UserGroup, ACLPermission +from .users import BaseUser, UserGroup, ACLPermission from .tasks import Task, PeriodicTask, History, HistoryLines, Template from .hooks import Hook from ..validators import RegexValidator from ..exceptions import UnknownTypeException from ..utils import raise_context, AnsibleArgumentsReference +from ..tasks import SendHook + + +##################################### +# FUNCTIONS +##################################### +def send_hook(when, target): + msg = OrderedDict(when=when) + msg['target'] = target + SendHook.delay(when, msg) + + +@raise_context() +def send_user_hook(when, instance): + send_hook( + when, OrderedDict( + user_id=instance.id, + username=instance.username, + admin=instance.is_staff + ) + ) + + +@raise_context() +def send_polemarch_models(when, instance, **kwargs): + target = OrderedDict(id=instance.id, name=instance.name, **kwargs) + send_hook(when, target) ##################################### @@ -164,3 +191,32 @@ def check_hook(instance, **kwargs): errors = instance.handlers.validate(instance) if errors: raise ValidationError(errors) + + +@receiver([signals.post_save, signals.post_delete], sender=BaseUser, + dispatch_uid='user_add_hook') +def user_add_hook(instance, **kwargs): + created = kwargs.get('created', None) + when = None + if created is None: + when = "on_user_del" + elif not created: + when = "on_user_upd" + elif created: + when = "on_user_add" + send_user_hook(when, instance) if when else None + + +@receiver([signals.post_save, signals.post_delete], sender=Project) +@receiver([signals.post_save, signals.post_delete], sender=PeriodicTask) +@receiver([signals.post_save, signals.post_delete], sender=Inventory) +@receiver([signals.post_save, signals.post_delete], sender=Group) +@receiver([signals.post_save, signals.post_delete], sender=Host) +def polemarch_hook(instance, **kwargs): + created = kwargs.get('created', None) + when = "on_object_add" + if created is None: + when = "on_object_del" + elif not created: + when = "on_object_upd" + send_polemarch_models(when, instance) diff --git a/polemarch/main/models/hooks.py b/polemarch/main/models/hooks.py index bd8146cb..1a54d4b5 100644 --- a/polemarch/main/models/hooks.py +++ b/polemarch/main/models/hooks.py @@ -5,7 +5,17 @@ class HookHandlers(ModelHandlers): - when_types = ['on_execution', 'after_execution'] + when_types_names = dict( + on_execution="Before start task", + after_execution="After end task", + on_user_add="When new user register", + on_user_upd="When user update data", + on_user_del="When user was removed", + on_object_add="When new Polemarch object was added", + on_object_upd="When Polemarch object was updated", + on_object_del="When Polemarch object was removed", + ) + when_types = when_types_names.keys() def get_handler(self, obj): return self[obj.type](obj, self.when_types, **self.opts(obj.type)) diff --git a/polemarch/main/tests/api.py b/polemarch/main/tests/api.py index 4a2b58ae..b96efbab 100644 --- a/polemarch/main/tests/api.py +++ b/polemarch/main/tests/api.py @@ -4,6 +4,11 @@ from django.contrib.auth.hashers import make_password from django.utils.timezone import now +try: + from mock import patch +except ImportError: + from unittest.mock import patch + from ..utils import redirect_stdany from ._base import BaseTestCase, User, json from .project import ApiProjectsTestCase @@ -183,17 +188,41 @@ def test_api_users_exists(self): self.result(client.post, "/api/v1/users/", 409, data) self._logout(client) - def test_api_users_insert_and_delete(self): + @patch('polemarch.main.hooks.http.Backend._execute') + def test_api_users_insert_and_delete(self, execute_method): + self.sended = False + hook_url = 'http://ex.com' + hook_data = dict( + name="test", type='HTTP', recipients=hook_url, when='on_user_add' + ) + user_data = { + "username": "test_user", "password": "eadgbe", + "is_active": True, "first_name": "user_f_name", + "last_name": "user_l_name", "email": "test@domain.lan" + } + for w in ['on_user_add', 'on_user_upd', 'on_user_del']: + hd = dict(**hook_data) + hd['when'] = w + self.post_result("/api/v1/hooks/", data=json.dumps(hd)) + + def side_effect_method(url, when, message): + self.assertEqual(url, hook_url) + self.assertEqual(when, 'on_user_add') + json.dumps(message) + self.assertEqual( + message['target']['username'], user_data['username'] + ) + self.sended = True + return '200 OK: {"result": "ok"}' + client = self._login() self.result(client.get, "/api/v1/users/") - result = self.result(client.post, "/api/v1/users/", 201, - {"username": "test_user", - "password": "eadgbe", - "is_active": True, - "first_name": "user_f_name", - "last_name": "user_l_name", - "email": "test@domain.lan" - }) + execute_method.reset_mock() + execute_method.side_effect = side_effect_method + result = self.result(client.post, "/api/v1/users/", 201, user_data) + self.assertEquals(execute_method.call_count, 2) + execute_method.reset_mock() + self.assertTrue(self.sended, "Raised on sending.") self.assertEqual(result["username"], "test_user") self.assertEqual(result["first_name"], "user_f_name") self.assertEqual(result["last_name"], "user_l_name") @@ -201,7 +230,15 @@ def test_api_users_insert_and_delete(self): id = str(result['id']) url = "/api/v1/users/{}/".format(id) self.assertRCode(client.get(url), 200) + result = self.result( + client.patch, url, + data=json.dumps({'last_name': 'tttt'}), + content_type="application/json" + ) + self.assertEquals(execute_method.call_count, 1) + execute_method.reset_mock() self.assertRCode(client.delete(url), 204) + self.assertEquals(execute_method.call_count, 1) idself = self.user.id url = "/api/v1/users/{}/".format(idself) self.assertRCode(client.delete(url), 409) diff --git a/polemarch/main/tests/tasks.py b/polemarch/main/tests/tasks.py index 6b308adc..fa3d851b 100644 --- a/polemarch/main/tests/tasks.py +++ b/polemarch/main/tests/tasks.py @@ -364,6 +364,12 @@ def test_hook_task(self, subprocess_function, execute_method): self.assertIn('SCRIPT', result['types']) self.assertIn('on_execution', result['when']) self.assertIn('after_execution', result['when']) + self.assertIn('on_user_add', result['when']) + self.assertIn('on_user_upd', result['when']) + self.assertIn('on_user_del', result['when']) + self.assertIn('on_object_add', result['when']) + self.assertIn('on_object_upd', result['when']) + self.assertIn('on_object_del', result['when']) inv, _ = self.create_inventory() hook_url = 'http://ex.com' hook_data = dict( @@ -399,8 +405,24 @@ def side_effect(url, when, message): data=json.dumps(dict(inventory=inv, playbook=playbook, sync=1)) ) self.assertTrue(self.sended, "Raised on sending.") - self.assertEquals(execute_method.call_count, 4) + self.assertEquals(execute_method.call_count, 2) self.assertEquals(subprocess_function.call_count, 1) + execute_method.reset_mock() + for when in ['on_object_add', 'on_object_upd', 'on_object_del']: + hook_data_obj = dict(**hook_data) + hook_data_obj['when'] = when + self.post_result("/api/v1/hooks/", data=json.dumps(hook_data_obj)) + range_int = 3 + for i in range(range_int): + self.get_model_class('Host').objects.create(name="h-{}".format(i)) + hosts = self.get_model_class('Host').objects.filter( + name__in=["h-{}".format(i) for i in range(range_int)] + ) + for h in hosts: + h.type = 'RANGE' + h.save() + hosts.delete() + self.assertEquals(execute_method.call_count, 9) @patch('polemarch.main.utils.CmdExecutor.execute') def test_execute_inventory_file(self, subprocess_function): From c31a19739519c743bbd7537c6afc6d2bb59d7bc0 Mon Sep 17 00:00:00 2001 From: Trapenok Victor Date: Thu, 21 Dec 2017 16:06:52 +1000 Subject: [PATCH 06/14] projects update cloud/polemarchplus#107 --- polemarch/static/js/pmInventories.js | 2 ++ polemarch/static/js/pmProjects.js | 25 ++++++++++++++++++++++- polemarch/static/templates/filedsLib.html | 8 ++++---- 3 files changed, 30 insertions(+), 5 deletions(-) diff --git a/polemarch/static/js/pmInventories.js b/polemarch/static/js/pmInventories.js index 7b9dfad9..a41a10fd 100644 --- a/polemarch/static/js/pmInventories.js +++ b/polemarch/static/js/pmInventories.js @@ -492,6 +492,8 @@ pmInventories.importInventory = function(inventory) if(!inventory.name) { + // inventory.name = "new imported inventory" + $.notify("Error in field inventory name", "error"); def2.reject() return def2.promise(); diff --git a/polemarch/static/js/pmProjects.js b/polemarch/static/js/pmProjects.js index d56f6c6e..29c60b2c 100644 --- a/polemarch/static/js/pmProjects.js +++ b/polemarch/static/js/pmProjects.js @@ -356,6 +356,11 @@ pmProjects.model.page_item = { name:'revision', title:'Revision', }, + { + filed: new filedsLib.filed.disabled(), + name:'status', + title:'Status', + }, ] ], /** @@ -395,13 +400,30 @@ pmProjects.model.page_item = { }, } +pmProjects.startUpdateProjectItem = function(item_id) +{ + var thisObj = this; + if(thisObj.model.items[item_id].status == "WAIT_SYNC" || thisObj.model.items[item_id].status == "SYNC") + { + thisObj.model.updateTimeoutId = setTimeout(function() + { + $.when(thisObj.loadItem(item_id)).always(function() + { + thisObj.startUpdateProjectItem(item_id) + }) + }, 5000) + } +} + pmProjects.openItem = function(holder, menuInfo, data) { + var item_id = data.reg[1] var def = new $.Deferred(); $.when(pmProjects.supportedRepos()).always(function() { $.when(pmProjects.showItem(holder, menuInfo, data)) .always(function() { + pmProjects.startUpdateProjectItem(item_id) def.resolve(); }) }).promise(); @@ -561,7 +583,8 @@ tabSignal.connect("polemarch.start", function() spajs.addMenu({ id:"project", urlregexp:[/^project\/([0-9]+)$/, /^projects\/([0-9]+)$/], - onOpen:function(holder, menuInfo, data){return pmProjects.openItem(holder, menuInfo, data);} + onOpen:function(holder, menuInfo, data){return pmProjects.openItem(holder, menuInfo, data);}, + onClose:function(){return pmHistory.stopUpdates();}, }) spajs.addMenu({ diff --git a/polemarch/static/templates/filedsLib.html b/polemarch/static/templates/filedsLib.html index c9d81a98..8a661d50 100644 --- a/polemarch/static/templates/filedsLib.html +++ b/polemarch/static/templates/filedsLib.html @@ -10,7 +10,7 @@ placeholder="<%- filed.placeholder %>" id="filed_<%- filed.name %>" <% if(item_id){ %> - <%= pmObj.model.items[item_id].justAttr(filed.name, 'value') %> + value="<%- pmObj.model.items[item_id][filed.name] %>" <% } %> >
<%- filed.help || '' %>
@@ -45,7 +45,7 @@ placeholder="<%- filed.placeholder %>" id="filed_<%- filed.name %>" <% if(item_id && false){ %> - <%= pmObj.model.items[item_id].justAttr(filed.name, 'value') %> + value="<%- pmObj.model.items[item_id][filed.name] %>" <% } %> >
<%- filed.help || '' %>
@@ -56,8 +56,8 @@ + + + - + + + +