diff --git a/apps/assets/api/asset.py b/apps/assets/api/asset.py index 1f75b49fc..8e9edc3e2 100644 --- a/apps/assets/api/asset.py +++ b/apps/assets/api/asset.py @@ -33,7 +33,7 @@ class AssetViewSet(IDInFilterMixin, LabelFilter, BulkModelViewSet): """ filter_fields = ("hostname", "ip") search_fields = filter_fields - ordering_fields = ("hostname", "ip", "port", "cluster", "cpu_cores") + ordering_fields = ("hostname", "ip", "port", "cpu_cores") queryset = Asset.objects.all() serializer_class = serializers.AssetSerializer pagination_class = LimitOffsetPagination @@ -47,13 +47,9 @@ class AssetViewSet(IDInFilterMixin, LabelFilter, BulkModelViewSet): if admin_user_id: admin_user = get_object_or_404(AdminUser, id=admin_user_id) - assets_direct = [asset.id for asset in admin_user.asset_set.all()] - clusters = [cluster.id for cluster in admin_user.cluster_set.all()] - queryset = queryset.filter(Q(cluster__id__in=clusters)|Q(id__in=assets_direct)) + queryset = queryset.filter(admin_user=admin_user) if system_user_id: system_user = get_object_or_404(SystemUser, id=system_user_id) - clusters = system_user.get_clusters() - queryset = queryset.filter(cluster__in=clusters) if node_id: node = get_object_or_404(Node, id=node_id) queryset = queryset.filter(nodes__key__startswith=node.key).distinct() diff --git a/apps/assets/api/system_user.py b/apps/assets/api/system_user.py index d19cf152c..c34690076 100644 --- a/apps/assets/api/system_user.py +++ b/apps/assets/api/system_user.py @@ -20,7 +20,7 @@ from common.utils import get_logger from ..hands import IsSuperUser, IsSuperUserOrAppUser from ..models import SystemUser from .. import serializers -from ..tasks import push_system_user_to_cluster_assets_manual, \ +from ..tasks import push_system_user_to_assets_manual, \ test_system_user_connectability_manual @@ -68,7 +68,7 @@ class SystemUserPushApi(generics.RetrieveAPIView): def retrieve(self, request, *args, **kwargs): system_user = self.get_object() - push_system_user_to_cluster_assets_manual.delay(system_user) + push_system_user_to_assets_manual.delay(system_user) return Response({"msg": "Task created"}) diff --git a/apps/assets/forms/asset.py b/apps/assets/forms/asset.py index 593c35456..8aaf8158b 100644 --- a/apps/assets/forms/asset.py +++ b/apps/assets/forms/asset.py @@ -34,7 +34,9 @@ class AssetCreateForm(forms.ModelForm): 'hostname': '* required', 'ip': '* required', 'port': '* required', - 'admin_user': _('') + 'admin_user': _('Admin user is a privilege user exist on this asset,' + 'Example: root or other NOPASSWD sudo privilege user' + ) } diff --git a/apps/assets/forms/user.py b/apps/assets/forms/user.py index 6488a6004..24807ca08 100644 --- a/apps/assets/forms/user.py +++ b/apps/assets/forms/user.py @@ -114,22 +114,22 @@ class SystemUserForm(PasswordAndKeyAuthForm): fields = [ 'name', 'username', 'protocol', 'auto_generate_key', 'password', 'private_key_file', 'auto_push', 'sudo', - 'comment', 'shell', 'cluster', 'priority', + 'comment', 'shell', 'nodes', 'priority', ] widgets = { 'name': forms.TextInput(attrs={'placeholder': _('Name')}), 'username': forms.TextInput(attrs={'placeholder': _('Username')}), - 'cluster': forms.SelectMultiple( + 'nodes': forms.SelectMultiple( attrs={ 'class': 'select2', - 'data-placeholder': _(' Select clusters') + 'data-placeholder': _('Nodes') } ), } help_texts = { 'name': '* required', 'username': '* required', - 'cluster': _('If auto push checked, system user will be create at cluster assets'), + 'nodes': _('If auto push checked, system user will be create at node assets'), 'auto_push': _('Auto push system user to asset'), 'priority': _('High level will be using login asset as default, if user was granted more than 2 system user'), } \ No newline at end of file diff --git a/apps/assets/hands.py b/apps/assets/hands.py index 403a08633..ad44052d3 100644 --- a/apps/assets/hands.py +++ b/apps/assets/hands.py @@ -11,8 +11,7 @@ """ -from users.utils import AdminUserRequiredMixin -from users.permissions import IsAppUser, IsSuperUser, IsValidUser, IsSuperUserOrAppUser +from common.mixins import AdminUserRequiredMixin +from common.permissions import IsAppUser, IsSuperUser, IsValidUser, IsSuperUserOrAppUser from users.models import User, UserGroup from perms.utils import NodePermissionUtil -from perms.tasks import push_users \ No newline at end of file diff --git a/apps/assets/models/__init__.py b/apps/assets/models/__init__.py index b4b0b8707..dbc703706 100644 --- a/apps/assets/models/__init__.py +++ b/apps/assets/models/__init__.py @@ -5,6 +5,6 @@ from .user import AdminUser, SystemUser from .label import Label from .cluster import * from .group import * -from .tree import * +from .node import * from .asset import * from .utils import * diff --git a/apps/assets/models/asset.py b/apps/assets/models/asset.py index d8e4ebe82..b101d63f9 100644 --- a/apps/assets/models/asset.py +++ b/apps/assets/models/asset.py @@ -29,8 +29,11 @@ def default_cluster(): def default_node(): - from .tree import Node - return Node.root() + try: + from .tree import Node + return Node.root() + except: + return None class Asset(models.Model): @@ -43,7 +46,7 @@ class Asset(models.Model): is_active = models.BooleanField(default=True, verbose_name=_('Is active')) # Auth - admin_user = models.ForeignKey('assets.AdminUser', on_delete=models.PROTECT, verbose_name=_("Admin user")) + admin_user = models.ForeignKey('assets.AdminUser', on_delete=models.PROTECT, verbose_name=_("Admin user")) # Some information public_ip = models.GenericIPAddressField(max_length=32, blank=True, null=True, verbose_name=_('Public IP')) @@ -102,30 +105,12 @@ class Asset(models.Model): else: return False - @property - def admin_user_avail(self): - if self.admin_user: - admin_user = self.admin_user - elif self.cluster and self.cluster.admin_user: - admin_user = self.cluster.admin_user - else: - return None - return admin_user - - @property - def is_has_private_admin_user(self): - if self.admin_user: - return True - else: - return False - def to_json(self): return { 'id': self.id, 'hostname': self.hostname, 'ip': self.ip, 'port': self.port, - 'groups': [group.name for group in self.groups.all()], } def _to_secret_json(self): @@ -136,13 +121,14 @@ class Asset(models.Model): Todo: May be move to ops implements it """ data = self.to_json() - if self.admin_user_avail: - admin_user = self.admin_user_avail + if self.admin_user: + admin_user = self.admin_user data.update({ 'username': admin_user.username, 'password': admin_user.password, 'private_key': admin_user.private_key_file, 'become': admin_user.become_info, + 'groups': [node.value for node in self.nodes.all()], }) return data @@ -161,7 +147,6 @@ class Asset(models.Model): asset = cls(ip='%s.%s.%s.%s' % (i, i, i, i), hostname=forgery_py.internet.user_name(True), admin_user=choice(AdminUser.objects.all()), - cluster=choice(Cluster.objects.all()), port=22, created_by='Fake') try: diff --git a/apps/assets/models/tree.py b/apps/assets/models/node.py similarity index 85% rename from apps/assets/models/tree.py rename to apps/assets/models/node.py index a62b3c1bd..7c5e8f450 100644 --- a/apps/assets/models/tree.py +++ b/apps/assets/models/node.py @@ -24,7 +24,7 @@ class Node(models.Model): if self == self.__class__.root(): return self.value else: - return '{}/{}'.format( self.value, self.parent.full_value) + return '{}/{}'.format(self.value, self.parent.full_value) @property def level(self): @@ -78,6 +78,19 @@ class Node(models.Model): else: return parent + @property + def ancestor(self): + if self.parent == self.__class__.root(): + return [self.__class__.root()] + else: + return [self.parent, *tuple(self.parent.ancestor)] + + @property + def ancestor_with_node(self): + ancestor = self.ancestor + ancestor.insert(0, self) + return ancestor + @classmethod def root(cls): obj, created = cls.objects.get_or_create( diff --git a/apps/assets/models/user.py b/apps/assets/models/user.py index e536f7902..a8c24420f 100644 --- a/apps/assets/models/user.py +++ b/apps/assets/models/user.py @@ -218,7 +218,7 @@ class SystemUser(AssetUser): (RDP_PROTOCOL, 'rdp'), ) - cluster = models.ManyToManyField('assets.Cluster', blank=True, verbose_name=_("Cluster")) + nodes = models.ManyToManyField('assets.Node', blank=True, verbose_name=_("Nodes")) priority = models.IntegerField(default=10, verbose_name=_("Priority")) protocol = models.CharField(max_length=16, choices=PROTOCOL_CHOICES, default='ssh', verbose_name=_('Protocol')) auto_push = models.BooleanField(default=True, verbose_name=_('Auto push')) @@ -228,21 +228,6 @@ class SystemUser(AssetUser): def __str__(self): return self.name - def get_clusters_assets(self): - from .asset import Asset - clusters = self.get_clusters() - return Asset.objects.filter(cluster__in=clusters) - - def get_clusters(self): - return self.cluster.all() - - def get_clusters_joined(self): - return ', '.join([cluster.name for cluster in self.get_clusters()]) - - @property - def assets_amount(self): - return len(self.get_clusters_assets()) - def to_json(self): return { 'id': self.id, @@ -266,6 +251,12 @@ class SystemUser(AssetUser): def reachable_assets(self): return self.assets_connective.get('contacted', []) + def is_need_push(self): + if self.auto_push and self.protocol == self.__class__.SSH_PROTOCOL: + return True + else: + return False + class Meta: ordering = ['name'] verbose_name = _("System user") diff --git a/apps/assets/signals.py b/apps/assets/signals.py index a02a16d20..20da657b1 100644 --- a/apps/assets/signals.py +++ b/apps/assets/signals.py @@ -3,4 +3,3 @@ from django.dispatch import Signal on_app_ready = Signal() -on_system_user_auth_changed = Signal(providing_args=['system_user']) diff --git a/apps/assets/signals_handler.py b/apps/assets/signals_handler.py index 659a40a40..95c57e4cb 100644 --- a/apps/assets/signals_handler.py +++ b/apps/assets/signals_handler.py @@ -1,15 +1,14 @@ # -*- coding: utf-8 -*- # -from django.db.models.signals import post_save, post_init +from django.db.models.signals import post_save, m2m_changed from django.dispatch import receiver -from django.utils.translation import gettext as _ from common.utils import get_logger -from .models import Asset, SystemUser, Cluster +from .models import Asset, SystemUser, Node from .tasks import update_assets_hardware_info_util, \ - test_asset_connectability_util, \ - push_system_user_util + test_asset_connectability_util, push_system_user_to_node, \ + push_node_system_users_to_asset logger = get_logger(__file__) @@ -25,92 +24,42 @@ def test_asset_conn_on_created(asset): test_asset_connectability_util.delay(asset) -def push_cluster_system_users_to_asset(asset): - if asset.cluster: - logger.info("Push cluster system user to asset: {}".format(asset)) - task_name = _("Push cluster system users to asset") - system_users = asset.cluster.systemuser_set.all() - push_system_user_util.delay(system_users, [asset], task_name) - - @receiver(post_save, sender=Asset, dispatch_uid="my_unique_identifier") def on_asset_created(sender, instance=None, created=False, **kwargs): if instance and created: logger.info("Asset `{}` create signal received".format(instance)) update_asset_hardware_info_on_created(instance) test_asset_conn_on_created(instance) - # push_cluster_system_users_to_asset(instance) -# def push_to_cluster_assets_on_system_user_created_or_update(system_user): -# if not system_user.auto_push: -# return -# logger.debug("Push system user `{}` to cluster assets".format(system_user.name)) -# for cluster in system_user.cluster.all(): -# task_name = _("Push system user to cluster assets: {}->{}").format( -# cluster.name, system_user.name -# ) -# assets = cluster.assets.all() -# push_system_user_util.delay([system_user], assets, task_name) +@receiver(post_save, sender=SystemUser, dispatch_uid="my_unique_identifier") +def on_system_user_update(sender, instance=None, created=True, **kwargs): + if instance and not created: + for node in instance.nodes.all(): + push_system_user_to_node(instance, node) -# @receiver(post_save, sender=SystemUser) -# def on_system_user_created_or_updated(sender, instance=None, **kwargs): -# if instance and instance.auto_push: -# logger.info("System user `{}` create or update signal received".format(instance)) -# push_to_cluster_assets_on_system_user_created_or_update(instance) -# -# -# @receiver(post_init, sender=Cluster, dispatch_uid="my_unique_identifier") -# def on_cluster_init(sender, instance, **kwargs): -# instance.__original_assets = tuple(instance.assets.values_list('pk', flat=True)) -# instance.__origin_system_users = tuple(instance.systemuser_set.values_list('pk', flat=True)) -# -# -# @receiver(post_save, sender=Cluster, dispatch_uid="my_unique_identifier") -# def on_cluster_assets_changed(sender, instance, **kwargs): -# assets_origin = instance.__original_assets -# assets_new = instance.assets.values_list('pk', flat=True) -# assets_added = set(assets_new) - set(assets_origin) -# if assets_added: -# logger.debug("Receive cluster change assets signal") -# logger.debug("Push cluster `{}` system users to: {}".format( -# instance, ', '.join([str(asset) for asset in assets_added]) -# )) -# assets = [] -# for asset_id in assets_added: -# try: -# asset = Asset.objects.get(pk=asset_id) -# except Asset.DoesNotExist: -# continue -# else: -# assets.append(asset) -# system_users = [s for s in instance.systemuser_set.all() if s.auto_push] -# task_name = _("Push system user to assets") -# push_system_user_util.delay(system_users, assets, task_name) -# -# -# @receiver(post_save, sender=Cluster, dispatch_uid="my_unique_identifier2") -# def on_cluster_system_user_changed(sender, instance, **kwargs): -# system_users_origin = instance.__origin_system_users -# system_user_new = instance.systemuser_set.values_list('pk', flat=True) -# system_users_added = set(system_user_new) - set(system_users_origin) -# if system_users_added: -# logger.debug("Receive cluster change system users signal") -# system_users = [] -# for system_user_id in system_users_added: -# try: -# system_user = SystemUser.objects.get(pk=system_user_id) -# except SystemUser.DoesNotExist: -# continue -# else: -# system_users.append(system_user) -# logger.debug("Push new system users `{}` to cluster `{}` assets".format( -# ','.join([s.name for s in system_users]), instance -# )) -# task_name = _( -# "Push system user to cluster assets: {}->{}").format( -# instance.name, ', '.join(s.name for s in system_users) -# ) - push_system_user_util.delay(system_users, instance.assets.all(), task_name) +@receiver(m2m_changed, sender=SystemUser.nodes.through) +def on_system_user_node_change(sender, instance=None, **kwargs): + if instance and kwargs["action"] == "post_add": + for pk in kwargs['pk_set']: + node = kwargs['model'].objects.get(pk=pk) + push_system_user_to_node(instance, node) + + +@receiver(m2m_changed, sender=Asset.nodes.through) +def on_asset_node_changed(sender, instance=None, **kwargs): + if isinstance(instance, Asset) and kwargs['action'] == 'post_add': + logger.debug("Asset node change signal received") + for pk in kwargs['pk_set']: + node = kwargs['model'].objects.get(pk=pk) + push_node_system_users_to_asset(node, [instance]) + + +@receiver(m2m_changed, sender=Asset.nodes.through) +def on_node_assets_changed(sender, instance=None, **kwargs): + if isinstance(instance, Node) and kwargs['action'] == 'post_add': + logger.debug("Node assets change signal received") + assets = kwargs['model'].objects.filter(pk__in=kwargs['pk_set']) + push_node_system_users_to_asset(instance, assets) diff --git a/apps/assets/tasks.py b/apps/assets/tasks.py index 4fc5b023b..25dec8af7 100644 --- a/apps/assets/tasks.py +++ b/apps/assets/tasks.py @@ -99,7 +99,7 @@ def update_assets_hardware_info_util(assets, task_name=None): result = task.run() # Todo: may be somewhere using # Manual run callback function - assets_updated = set_assets_hardware_info(result) + set_assets_hardware_info(result) return result @@ -262,21 +262,23 @@ def test_system_user_connectability_util(system_user, task_name): :param task_name: :return: """ - from ops.utils import update_or_create_ansible_task - assets = system_user.get_clusters_assets() - hosts = [asset.hostname for asset in assets] - tasks = const.TEST_SYSTEM_USER_CONN_TASKS - if not hosts: - logger.info("No hosts, passed") - return {} - task, created = update_or_create_ansible_task( - task_name, hosts=hosts, tasks=tasks, pattern='all', - options=const.TASK_OPTIONS, - run_as=system_user.name, created_by="System", - ) - result = task.run() - set_system_user_connectablity_info(result, system_user=system_user.name) - return result + # todo + # from ops.utils import update_or_create_ansible_task + # assets = system_user.get_clusters_assets() + # hosts = [asset.hostname for asset in assets] + # tasks = const.TEST_SYSTEM_USER_CONN_TASKS + # if not hosts: + # logger.info("No hosts, passed") + # return {} + # task, created = update_or_create_ansible_task( + # task_name, hosts=hosts, tasks=tasks, pattern='all', + # options=const.TASK_OPTIONS, + # run_as=system_user.name, created_by="System", + # ) + # result = task.run() + # set_system_user_connectablity_info(result, system_user=system_user.name) + # return result + return {} @shared_task @@ -290,21 +292,23 @@ def test_system_user_connectability_manual(system_user): @after_app_ready_start @after_app_shutdown_clean def test_system_user_connectability_period(): - from ops.utils import update_or_create_ansible_task - system_users = SystemUser.objects.all() - for system_user in system_users: - task_name = _("Test system user connectability period: {}").format( - system_user.name - ) - assets = system_user.get_clusters_assets() - hosts = [asset.hostname for asset in assets] - tasks = const.TEST_SYSTEM_USER_CONN_TASKS - update_or_create_ansible_task( - task_name=task_name, hosts=hosts, tasks=tasks, pattern='all', - options=const.TASK_OPTIONS, run_as_admin=False, run_as=system_user.name, - created_by='System', interval=3600, is_periodic=True, - callback=set_admin_user_connectability_info.name, - ) + # Todo + pass + # from ops.utils import update_or_create_ansible_task + # system_users = SystemUser.objects.all() + # for system_user in system_users: + # task_name = _("Test system user connectability period: {}").format( + # system_user.name + # ) + # assets = system_user.get_clusters_assets() + # hosts = [asset.hostname for asset in assets] + # tasks = const.TEST_SYSTEM_USER_CONN_TASKS + # update_or_create_ansible_task( + # task_name=task_name, hosts=hosts, tasks=tasks, pattern='all', + # options=const.TASK_OPTIONS, run_as_admin=False, run_as=system_user.name, + # created_by='System', interval=3600, is_periodic=True, + # callback=set_admin_user_connectability_info.name, + # ) #### Push system user tasks #### @@ -315,7 +319,6 @@ def get_push_system_user_tasks(system_user): return [] tasks = [] - if system_user.password: tasks.append({ 'name': 'Add user {}'.format(system_user.username), @@ -358,7 +361,8 @@ def push_system_user_util(system_users, assets, task_name): from ops.utils import update_or_create_ansible_task tasks = [] for system_user in system_users: - tasks.extend(get_push_system_user_tasks(system_user)) + if system_user.is_need_push(): + tasks.extend(get_push_system_user_tasks(system_user)) if not tasks: logger.info("Not tasks, passed") @@ -375,11 +379,41 @@ def push_system_user_util(system_users, assets, task_name): return task.run() +def get_node_push_system_user_task_name(system_user, node): + return _("Push system user to node: {} => {}").format( + system_user.name, + node.value + ) + + +def push_system_user_to_node(system_user, node): + assets = node.get_all_assets() + task_name = get_node_push_system_user_task_name(system_user, node) + push_system_user_util.delay([system_user], assets, task_name) + + @shared_task -def push_system_user_to_cluster_assets_manual(system_user): - task_name = _("Push system user to cluster assets: {}").format(system_user.name) - assets = system_user.get_clusters_assets() - return push_system_user_util([system_user], assets, task_name) +def push_system_user_related_nodes(system_user): + nodes = system_user.nodes.all() + for node in nodes: + push_system_user_to_node(system_user, node) + + +@shared_task +def push_system_user_to_assets_manual(system_user): + push_system_user_related_nodes(system_user) + + +def push_node_system_users_to_asset(node, assets): + system_users = [] + nodes = node.ancestor_with_node + # 获取该节点所有父节点有的系统用户, 然后推送 + for n in nodes: + system_users.extend(list(n.systemuser_set.all())) + + if system_users: + task_name = _("Push system users to node: {}").format(node.value) + push_system_user_util.delay(system_users, assets, task_name) @shared_task @@ -387,23 +421,5 @@ def push_system_user_to_cluster_assets_manual(system_user): @after_app_ready_start @after_app_shutdown_clean def push_system_user_period(): - from ops.utils import update_or_create_ansible_task - clusters = Cluster.objects.all() - - for cluster in clusters: - tasks = [] - system_users = [system_user for system_user in cluster.systemuser_set.all() if system_user.auto_push] - if not system_users: - return - for system_user in system_users: - tasks.extend(get_push_system_user_tasks(system_user)) - - task_name = _("Push cluster system users to assets period: {}").format( - cluster.name - ) - hosts = [asset.hostname for asset in cluster.assets.all()] - update_or_create_ansible_task( - task_name=task_name, hosts=hosts, tasks=tasks, pattern='all', - options=const.TASK_OPTIONS, run_as_admin=True, created_by='System', - interval=60*60*24, is_periodic=True, - ) + for system_user in SystemUser.objects.all(): + push_system_user_related_nodes(system_user) diff --git a/apps/assets/templates/assets/_system_user.html b/apps/assets/templates/assets/_system_user.html index 4ebb64279..fe06a6fc7 100644 --- a/apps/assets/templates/assets/_system_user.html +++ b/apps/assets/templates/assets/_system_user.html @@ -39,7 +39,6 @@ {% bootstrap_field form.username layout="horizontal" %} {% bootstrap_field form.priority layout="horizontal" %} {% bootstrap_field form.protocol layout="horizontal" %} - {% bootstrap_field form.cluster layout="horizontal" %} {% block auth %}