mirror of https://github.com/jumpserver/jumpserver
perf: 修改改密
parent
f921f12171
commit
4e5a7a0a25
|
@ -0,0 +1 @@
|
|||
from .methods import platform_automation_methods
|
|
@ -0,0 +1,14 @@
|
|||
## all connection vars
|
||||
hostname asset_name=name asset_type=type asset_primary_protocol=ssh asset_primary_port=22 asset_protocols=[]
|
||||
|
||||
## local connection
|
||||
hostname ansible_connection=local
|
||||
|
||||
## local connection with gateway
|
||||
hostname ansible_connection=ssh ansible_user=gateway.username ansible_port=gateway.port ansible_host=gateway.host ansible_ssh_private_key_file=gateway.key
|
||||
|
||||
## ssh connection for windows
|
||||
hostname ansible_connection=ssh ansible_shell_type=powershell/cmd ansible_user=windows.username ansible_port=windows.port ansible_host=windows.host ansible_ssh_private_key_file=windows.key
|
||||
|
||||
## ssh connection
|
||||
hostname ansible_user=user ansible_password=pass ansible_host=host ansible_port=port ansible_ssh_private_key_file=key ssh_args="-o StrictHostKeyChecking=no"
|
|
@ -0,0 +1,70 @@
|
|||
import os
|
||||
|
||||
from django.conf import settings
|
||||
from django.utils import timezone
|
||||
|
||||
from ops.ansible import JMSInventory
|
||||
|
||||
|
||||
class BasePlaybookManager:
|
||||
ansible_account_policy = 'privileged_first'
|
||||
|
||||
def __init__(self, execution):
|
||||
self.execution = execution
|
||||
self.automation = execution.automation
|
||||
|
||||
def get_grouped_assets(self):
|
||||
return self.automation.all_assets_group_by_platform()
|
||||
|
||||
@property
|
||||
def playbook_dir_path(self):
|
||||
ansible_dir = settings.ANSIBLE_DIR
|
||||
path = os.path.join(
|
||||
ansible_dir, self.automation.type, self.automation.name,
|
||||
timezone.now().strftime('%Y%m%d_%H%M%S')
|
||||
)
|
||||
return path
|
||||
|
||||
@property
|
||||
def inventory_path(self):
|
||||
return os.path.join(self.playbook_dir_path, 'inventory', 'hosts.json')
|
||||
|
||||
@property
|
||||
def playbook_path(self):
|
||||
return os.path.join(self.playbook_dir_path, 'project', 'main.yml')
|
||||
|
||||
def generate(self):
|
||||
self.prepare_playbook_dir()
|
||||
self.generate_inventory()
|
||||
self.generate_playbook()
|
||||
|
||||
def prepare_playbook_dir(self):
|
||||
inventory_dir = os.path.dirname(self.inventory_path)
|
||||
playbook_dir = os.path.dirname(self.playbook_path)
|
||||
for d in [inventory_dir, playbook_dir]:
|
||||
if not os.path.exists(d):
|
||||
os.makedirs(d, exist_ok=True, mode=0o755)
|
||||
|
||||
def inventory_kwargs(self):
|
||||
raise NotImplemented
|
||||
|
||||
def generate_inventory(self):
|
||||
inventory = JMSInventory(
|
||||
assets=self.automation.get_all_assets(),
|
||||
account_policy=self.ansible_account_policy,
|
||||
**self.inventory_kwargs()
|
||||
)
|
||||
inventory.write_to_file(self.inventory_path)
|
||||
print("Generate inventory done: {}".format(self.inventory_path))
|
||||
|
||||
def generate_playbook(self):
|
||||
raise NotImplemented
|
||||
|
||||
def get_runner(self):
|
||||
raise NotImplemented
|
||||
|
||||
def run(self, **kwargs):
|
||||
self.generate()
|
||||
runner = self.get_runner()
|
||||
return runner.run(**kwargs)
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
- hosts: demo
|
||||
tasks:
|
||||
- name: ping
|
||||
ping:
|
||||
|
||||
#- name: print variables
|
||||
# debug:
|
||||
# msg: "Username: {{ account.username }}, Password: {{ account.password }}"
|
||||
|
||||
- name: Change password
|
||||
user:
|
||||
name: "{{ account.username }}"
|
||||
password: "{{ account.password | password_hash('des') }}"
|
||||
update_password: always
|
||||
when: account.password
|
||||
|
||||
- name: Change public key
|
||||
authorized_key:
|
||||
user: "{{ account.username }}"
|
||||
key: "{{ account.public_key }}"
|
||||
state: present
|
||||
when: account.public_key
|
||||
|
||||
- name: Verify password
|
||||
ping:
|
||||
vars:
|
||||
ansible_user: "{{ account.username }}"
|
||||
ansible_pass: "{{ account.password }}"
|
||||
ansible_ssh_connection: paramiko
|
|
@ -0,0 +1,29 @@
|
|||
- hosts: demo
|
||||
tasks:
|
||||
- name: ping
|
||||
ping:
|
||||
|
||||
#- name: print variables
|
||||
# debug:
|
||||
# msg: "Username: {{ account.username }}, Password: {{ account.password }}"
|
||||
|
||||
- name: Change password
|
||||
user:
|
||||
name: "{{ account.username }}"
|
||||
password: "{{ account.password | password_hash('des') }}"
|
||||
update_password: always
|
||||
when: account.password
|
||||
|
||||
- name: Change public key
|
||||
authorized_key:
|
||||
user: "{{ account.username }}"
|
||||
key: "{{ account.public_key }}"
|
||||
state: present
|
||||
when: account.public_key
|
||||
|
||||
- name: Verify password
|
||||
ping:
|
||||
vars:
|
||||
ansible_user: "{{ account.username }}"
|
||||
ansible_pass: "{{ account.password }}"
|
||||
ansible_ssh_connection: paramiko
|
|
@ -0,0 +1,2 @@
|
|||
# all base inventory in base/base_inventory.txt
|
||||
asset_name(ip)_account_username account={"username": "", "password": "xxx"} ...base_inventory_vars
|
|
@ -0,0 +1,29 @@
|
|||
- hosts: demo
|
||||
tasks:
|
||||
- name: ping
|
||||
ping:
|
||||
|
||||
#- name: print variables
|
||||
# debug:
|
||||
# msg: "Username: {{ account.username }}, Password: {{ account.password }}"
|
||||
|
||||
- name: Change password
|
||||
user:
|
||||
name: "{{ account.username }}"
|
||||
password: "{{ account.password | password_hash('des') }}"
|
||||
update_password: always
|
||||
when: account.password
|
||||
|
||||
- name: Change public key
|
||||
authorized_key:
|
||||
user: "{{ account.username }}"
|
||||
key: "{{ account.public_key }}"
|
||||
state: present
|
||||
when: account.public_key
|
||||
|
||||
- name: Verify password
|
||||
ping:
|
||||
vars:
|
||||
ansible_user: "{{ account.username }}"
|
||||
ansible_pass: "{{ account.password }}"
|
||||
ansible_ssh_connection: paramiko
|
|
@ -0,0 +1,29 @@
|
|||
- hosts: demo
|
||||
tasks:
|
||||
- name: Test privileged account
|
||||
ping:
|
||||
|
||||
#- name: print variables
|
||||
# debug:
|
||||
# msg: "Username: {{ account.username }}, Password: {{ account.password }}"
|
||||
|
||||
- name: Change password
|
||||
user:
|
||||
name: "{{ account.username }}"
|
||||
password: "{{ account.password | password_hash('des') }}"
|
||||
update_password: always
|
||||
when: account.secret_type == 'password'
|
||||
|
||||
- name: Change public key
|
||||
authorized_key:
|
||||
user: "{{ account.username }}"
|
||||
key: "{{ account.public_key }}"
|
||||
state: present
|
||||
when: account.public_key
|
||||
|
||||
- name: Verify password
|
||||
ping:
|
||||
vars:
|
||||
ansible_user: "{{ account.username }}"
|
||||
ansible_pass: "{{ account.password }}"
|
||||
ansible_ssh_connection: paramiko
|
|
@ -0,0 +1,29 @@
|
|||
- hosts: demo
|
||||
tasks:
|
||||
- name: ping
|
||||
ping:
|
||||
|
||||
#- name: print variables
|
||||
# debug:
|
||||
# msg: "Username: {{ account.username }}, Password: {{ account.password }}"
|
||||
|
||||
- name: Change password
|
||||
user:
|
||||
name: "{{ account.username }}"
|
||||
password: "{{ account.password | password_hash('des') }}"
|
||||
update_password: always
|
||||
when: account.password
|
||||
|
||||
- name: Change public key
|
||||
authorized_key:
|
||||
user: "{{ account.username }}"
|
||||
key: "{{ account.public_key }}"
|
||||
state: present
|
||||
when: account.public_key
|
||||
|
||||
- name: Verify password
|
||||
ping:
|
||||
vars:
|
||||
ansible_user: "{{ account.username }}"
|
||||
ansible_pass: "{{ account.password }}"
|
||||
ansible_ssh_connection: paramiko
|
|
@ -0,0 +1,91 @@
|
|||
import os
|
||||
import shutil
|
||||
from copy import deepcopy
|
||||
from collections import defaultdict
|
||||
|
||||
import yaml
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
from ops.ansible import PlaybookRunner, JMSInventory
|
||||
from ..base.manager import BasePlaybookManager
|
||||
from assets.automations.methods import platform_automation_methods
|
||||
|
||||
|
||||
class ChangePasswordManager(BasePlaybookManager):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.id_method_mapper = {
|
||||
method['id']: method
|
||||
for method in platform_automation_methods
|
||||
}
|
||||
self.method_hosts_mapper = defaultdict(list)
|
||||
|
||||
def host_duplicator(self, host, asset=None, account=None, platform=None, **kwargs):
|
||||
accounts = asset.accounts.all()
|
||||
if account:
|
||||
accounts = accounts.exclude(id=account.id)
|
||||
if '*' not in self.automation.accounts:
|
||||
accounts = accounts.filter(username__in=self.automation.accounts)
|
||||
|
||||
automation = platform.automation
|
||||
change_password_enabled = automation and \
|
||||
automation.change_password_enabled and \
|
||||
automation.change_password_method and \
|
||||
automation.change_password_method in self.id_method_mapper
|
||||
|
||||
if not change_password_enabled:
|
||||
host.exclude = _('Change password disabled')
|
||||
return [host]
|
||||
|
||||
hosts = []
|
||||
for account in accounts:
|
||||
h = deepcopy(host)
|
||||
h['name'] += '_' + account.username
|
||||
h['account'] = {
|
||||
'name': account.name,
|
||||
'username': account.username,
|
||||
'secret_type': account.secret_type,
|
||||
'secret': account.secret,
|
||||
}
|
||||
hosts.append(h)
|
||||
self.method_hosts_mapper[automation.change_password_method].append(h['name'])
|
||||
return hosts
|
||||
|
||||
def inventory_kwargs(self):
|
||||
return {
|
||||
'host_duplicator': self.host_duplicator
|
||||
}
|
||||
|
||||
def generate_playbook(self):
|
||||
playbook = []
|
||||
for method_id, host_names in self.method_hosts_mapper.items():
|
||||
method = self.id_method_mapper[method_id]
|
||||
playbook_dir_path = method['dir']
|
||||
playbook_dir_name = os.path.dirname(playbook_dir_path)
|
||||
shutil.copytree(playbook_dir_path, self.playbook_dir_path)
|
||||
sub_playbook_path = os.path.join(self.playbook_dir_path, playbook_dir_name, 'main.yml')
|
||||
|
||||
with open(sub_playbook_path, 'r') as f:
|
||||
host_playbook_play = yaml.safe_load(f)
|
||||
|
||||
plays = []
|
||||
for name in host_names:
|
||||
play = deepcopy(host_playbook_play)
|
||||
play['hosts'] = name
|
||||
plays.append(play)
|
||||
|
||||
with open(sub_playbook_path, 'w') as f:
|
||||
yaml.safe_dump(plays, f, default_flow_style=False)
|
||||
|
||||
playbook.append({
|
||||
'name': method['name'],
|
||||
'import_playbook': playbook_dir_name + '/' + 'main.yml'
|
||||
})
|
||||
|
||||
with open(self.playbook_path, 'w') as f:
|
||||
yaml.safe_dump(playbook, f, default_flow_style=False)
|
||||
|
||||
print("Generate playbook done: " + self.playbook_path)
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
# from .backup.manager import AccountBackupExecutionManager
|
||||
#
|
||||
#
|
||||
class ExecutionManager:
|
||||
manager_type = {
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
import os
|
||||
import yaml
|
||||
import json
|
||||
from functools import partial
|
||||
|
||||
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
|
@ -62,4 +63,4 @@ platform_automation_methods = get_platform_automation_methods()
|
|||
|
||||
|
||||
if __name__ == '__main__':
|
||||
print(get_platform_automation_methods())
|
||||
print(json.dumps(platform_automation_methods, indent=4))
|
|
@ -38,7 +38,7 @@ class AllTypes(ChoicesMixin):
|
|||
|
||||
@classmethod
|
||||
def set_automation_methods(cls, category, tp, constraints):
|
||||
from assets.playbooks import filter_platform_methods
|
||||
from assets.automations import filter_platform_methods
|
||||
automation = constraints.get('automation', {})
|
||||
automation_methods = {}
|
||||
for item, enabled in automation.items():
|
||||
|
|
|
@ -10,6 +10,7 @@ from .gathered_user import *
|
|||
from .favorite_asset import *
|
||||
from .account import *
|
||||
from .backup import *
|
||||
from .automations import *
|
||||
from ._user import *
|
||||
# 废弃以下
|
||||
# from ._authbook import *
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
import logging
|
||||
import uuid
|
||||
from collections import defaultdict
|
||||
|
||||
from django.db import models
|
||||
from django.db.models import Q
|
||||
|
@ -41,6 +42,12 @@ class AssetQuerySet(models.QuerySet):
|
|||
def has_protocol(self, name):
|
||||
return self.filter(protocols__contains=name)
|
||||
|
||||
def group_by_platform(self) -> dict:
|
||||
groups = defaultdict(list)
|
||||
for asset in self.all():
|
||||
groups[asset.platform].append(asset)
|
||||
return groups
|
||||
|
||||
|
||||
class NodesRelationMixin:
|
||||
NODES_CACHE_KEY = 'ASSET_NODES_{}'
|
||||
|
@ -126,6 +133,22 @@ class Asset(AbsConnectivity, NodesRelationMixin, JMSOrgBaseModel):
|
|||
names.append(n.name + ':' + n.value)
|
||||
return names
|
||||
|
||||
@lazyproperty
|
||||
def primary_protocol(self):
|
||||
return self.protocols.first()
|
||||
|
||||
@lazyproperty
|
||||
def protocol(self):
|
||||
if not self.primary_protocol:
|
||||
return 'none'
|
||||
return self.primary_protocol.name
|
||||
|
||||
@lazyproperty
|
||||
def port(self):
|
||||
if not self.primary_protocol:
|
||||
return 0
|
||||
return self.primary_protocol.port
|
||||
|
||||
@property
|
||||
def protocols_as_list(self):
|
||||
return [{'name': p.name, 'port': p.port} for p in self.protocols.all()]
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from ops.const import StrategyChoice
|
||||
from ops.ansible.runner import PlaybookRunner
|
||||
from .base import BaseAutomation
|
||||
|
||||
|
|
@ -8,16 +8,17 @@ from common.db.fields import EncryptJsonDictTextField
|
|||
from orgs.mixins.models import OrgModelMixin, JMSOrgBaseModel
|
||||
from ops.mixin import PeriodTaskModelMixin
|
||||
from ops.tasks import execute_automation_strategy
|
||||
from ops.task_handlers import ExecutionManager
|
||||
from assets.models import Node, Asset
|
||||
from assets.automations.endpoint import ExecutionManager
|
||||
|
||||
|
||||
class BaseAutomation(JMSOrgBaseModel, PeriodTaskModelMixin):
|
||||
accounts = models.JSONField(default=list, verbose_name=_("Accounts"))
|
||||
nodes = models.ManyToManyField(
|
||||
'assets.Node', related_name='automation_strategy', blank=True, verbose_name=_("Nodes")
|
||||
'assets.Node', blank=True, verbose_name=_("Nodes")
|
||||
)
|
||||
assets = models.ManyToManyField(
|
||||
'assets.Asset', related_name='automation_strategy', blank=True, verbose_name=_("Assets")
|
||||
'assets.Asset', blank=True, verbose_name=_("Assets")
|
||||
)
|
||||
type = models.CharField(max_length=16, verbose_name=_('Type'))
|
||||
comment = models.TextField(blank=True, verbose_name=_('Comment'))
|
||||
|
@ -25,6 +26,17 @@ class BaseAutomation(JMSOrgBaseModel, PeriodTaskModelMixin):
|
|||
def __str__(self):
|
||||
return self.name + '@' + str(self.created_by)
|
||||
|
||||
def get_all_assets(self):
|
||||
nodes = self.nodes.all()
|
||||
node_asset_ids = Node.get_nodes_all_assets(*nodes).values_list('id', flat=True)
|
||||
direct_asset_ids = self.assets.all().values_list('id', flat=True)
|
||||
asset_ids = set(list(direct_asset_ids) + list(node_asset_ids))
|
||||
return Asset.objects.filter(id__in=asset_ids)
|
||||
|
||||
def all_assets_group_by_platform(self):
|
||||
assets = self.get_all_assets().prefetch_related('platform')
|
||||
return assets.group_by_platform()
|
||||
|
||||
def get_register_task(self):
|
||||
name = "automation_strategy_period_{}".format(str(self.id)[:8])
|
||||
task = execute_automation_strategy.name
|
||||
|
@ -40,13 +52,15 @@ class BaseAutomation(JMSOrgBaseModel, PeriodTaskModelMixin):
|
|||
'nodes': list(self.assets.all().values_list('id', flat=True)),
|
||||
}
|
||||
|
||||
def execute(self, trigger):
|
||||
def execute(self, trigger=Trigger.manual):
|
||||
try:
|
||||
eid = current_task.request.id
|
||||
except AttributeError:
|
||||
eid = str(uuid.uuid4())
|
||||
execution = AutomationStrategyExecution.objects.create(
|
||||
id=eid, strategy=self, snapshot=self.to_attr_json(), trigger=trigger
|
||||
|
||||
execution = AutomationExecution.objects.create(
|
||||
id=eid, strategy=self, trigger=trigger,
|
||||
snapshot=self.to_attr_json(),
|
||||
)
|
||||
return execution.start()
|
||||
|
||||
|
@ -55,22 +69,22 @@ class BaseAutomation(JMSOrgBaseModel, PeriodTaskModelMixin):
|
|||
verbose_name = _("Automation plan")
|
||||
|
||||
|
||||
class AutomationStrategyExecution(OrgModelMixin):
|
||||
class AutomationExecution(OrgModelMixin):
|
||||
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
||||
|
||||
date_created = models.DateTimeField(auto_now_add=True)
|
||||
timedelta = models.FloatField(default=0.0, verbose_name=_('Time'), null=True)
|
||||
date_start = models.DateTimeField(auto_now_add=True, verbose_name=_('Date start'))
|
||||
|
||||
automation = models.ForeignKey(
|
||||
'BaseAutomation', related_name='executions', on_delete=models.CASCADE,
|
||||
verbose_name=_('Automation strategy')
|
||||
)
|
||||
status = models.CharField(max_length=16, default='pending')
|
||||
date_created = models.DateTimeField(auto_now_add=True, verbose_name=_('Date created'))
|
||||
date_start = models.DateTimeField(null=True, verbose_name=_('Date start'), db_index=True)
|
||||
date_finished = models.DateTimeField(null=True, verbose_name=_("Date finished"))
|
||||
snapshot = EncryptJsonDictTextField(
|
||||
default=dict, blank=True, null=True, verbose_name=_('Automation snapshot')
|
||||
)
|
||||
strategy = models.ForeignKey(
|
||||
'BaseAutomation', related_name='execution', on_delete=models.CASCADE,
|
||||
verbose_name=_('Automation strategy')
|
||||
)
|
||||
trigger = models.CharField(
|
||||
max_length=128, default=Trigger.manual, choices=Trigger.choices, verbose_name=_('Trigger mode')
|
||||
max_length=128, default=Trigger.manual, choices=Trigger.choices,
|
||||
verbose_name=_('Trigger mode')
|
||||
)
|
||||
|
||||
class Meta:
|
||||
|
@ -83,28 +97,3 @@ class AutomationStrategyExecution(OrgModelMixin):
|
|||
def start(self):
|
||||
manager = ExecutionManager(execution=self)
|
||||
return manager.run()
|
||||
|
||||
|
||||
class AutomationStrategyTask(OrgModelMixin):
|
||||
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
||||
asset = models.ForeignKey(
|
||||
'assets.Asset', on_delete=models.CASCADE, verbose_name=_('Asset')
|
||||
)
|
||||
account = models.ForeignKey(
|
||||
'assets.Account', on_delete=models.CASCADE, verbose_name=_('Account')
|
||||
)
|
||||
is_success = models.BooleanField(default=False, verbose_name=_('Is success'))
|
||||
timedelta = models.FloatField(default=0.0, null=True, verbose_name=_('Time'))
|
||||
date_start = models.DateTimeField(auto_now_add=True, verbose_name=_('Date start'))
|
||||
reason = models.CharField(max_length=1024, blank=True, null=True, verbose_name=_('Reason'))
|
||||
execution = models.ForeignKey(
|
||||
'AutomationStrategyExecution', related_name='task', on_delete=models.CASCADE,
|
||||
verbose_name=_('Automation strategy execution')
|
||||
)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('Automation strategy task')
|
||||
|
||||
@property
|
||||
def handler_type(self):
|
||||
return self.execution.snapshot['type']
|
|
@ -2,7 +2,7 @@ from django.db import models
|
|||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from common.db import fields
|
||||
from ops.const import SSHKeyStrategy, PasswordStrategy, StrategyChoice
|
||||
from ops.const import PasswordStrategy, StrategyChoice
|
||||
from ops.utils import generate_random_password
|
||||
from .base import BaseAutomation
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
import os
|
||||
import time
|
||||
import shutil
|
||||
from typing import List
|
||||
|
||||
from django.conf import settings
|
||||
|
||||
from assets.models import Asset
|
||||
|
||||
|
||||
class BaseRunner:
|
||||
src_filepath: str
|
||||
|
||||
def __init__(self, assets: List[Asset], strategy):
|
||||
self.assets = assets
|
||||
self.strategy = strategy
|
||||
self.temp_folder = self.temp_folder_path()
|
||||
|
||||
@staticmethod
|
||||
def temp_folder_path():
|
||||
project_dir = settings.PROJECT_DIR
|
||||
tmp_dir = os.path.join(project_dir, 'tmp')
|
||||
filepath = os.path.join(tmp_dir, str(time.time()))
|
||||
return filepath
|
||||
|
||||
def del_temp_folder(self):
|
||||
shutil.rmtree(self.temp_folder)
|
||||
|
||||
def generate_temp_playbook(self):
|
||||
src = self.src_filepath
|
||||
dst = os.path.join(self.temp_folder, self.strategy)
|
||||
shutil.copytree(src, dst)
|
||||
return dst
|
|
@ -1,47 +0,0 @@
|
|||
import os
|
||||
import tempfile
|
||||
import shutil
|
||||
from typing import List
|
||||
|
||||
from django.conf import settings
|
||||
|
||||
from assets.models import Asset
|
||||
|
||||
|
||||
class BasePlaybookGenerator:
|
||||
def __init__(self, assets: list[Asset], strategy, ansible_connection='ssh'):
|
||||
self.assets = assets
|
||||
self.strategy = strategy
|
||||
self.playbook_dir = self.temp_folder_path()
|
||||
|
||||
def generate(self):
|
||||
self.prepare_playbook_dir()
|
||||
self.generate_inventory()
|
||||
self.generate_playbook()
|
||||
|
||||
def prepare_playbook_dir(self):
|
||||
pass
|
||||
|
||||
def generate_inventory(self):
|
||||
pass
|
||||
|
||||
def generate_playbook(self):
|
||||
pass
|
||||
|
||||
@property
|
||||
def base_dir(self):
|
||||
tmp_dir = os.path.join(settings.PROJECT_DIR, 'tmp')
|
||||
path = os.path.join(tmp_dir, self.strategy)
|
||||
return path
|
||||
|
||||
def temp_folder_path(self):
|
||||
return tempfile.mkdtemp(dir=self.base_dir)
|
||||
|
||||
def del_temp_folder(self):
|
||||
shutil.rmtree(self.playbook_dir)
|
||||
|
||||
def generate_temp_playbook(self):
|
||||
src = self.src_filepath
|
||||
dst = os.path.join(self.temp_folder, self.strategy)
|
||||
shutil.copytree(src, dst)
|
||||
return dst
|
|
@ -1,10 +0,0 @@
|
|||
{% for account in accounts %}
|
||||
- hosts: {{ account.asset.name }}
|
||||
vars:
|
||||
account:
|
||||
username: {{ account.username }}
|
||||
password: {{ account.password }}
|
||||
public_key: {{ account.public_key }}
|
||||
roles:
|
||||
- change_password
|
||||
{% endfor %}
|
|
@ -1,27 +0,0 @@
|
|||
- name: ping
|
||||
ping:
|
||||
|
||||
#- name: print variables
|
||||
# debug:
|
||||
# msg: "Username: {{ account.username }}, Password: {{ account.password }}"
|
||||
|
||||
- name: Change password
|
||||
user:
|
||||
name: "{{ account.username }}"
|
||||
password: "{{ account.password | password_hash('des') }}"
|
||||
update_password: always
|
||||
when: account.password
|
||||
|
||||
- name: Change public key
|
||||
authorized_key:
|
||||
user: "{{ account.username }}"
|
||||
key: "{{ account.public_key }}"
|
||||
state: present
|
||||
when: account.public_key
|
||||
|
||||
- name: Verify password
|
||||
ping:
|
||||
vars:
|
||||
ansible_user: "{{ account.username }}"
|
||||
ansible_pass: "{{ account.password }}"
|
||||
ansible_ssh_connection: paramiko
|
|
@ -1,10 +0,0 @@
|
|||
{% for account in accounts %}
|
||||
- hosts: {{ account.asset.name }}
|
||||
vars:
|
||||
account:
|
||||
username: {{ account.username }}
|
||||
password: {{ account.password }}
|
||||
public_key: {{ account.public_key }}
|
||||
roles:
|
||||
- change_password
|
||||
{% endfor %}
|
|
@ -1,27 +0,0 @@
|
|||
- name: ping
|
||||
ping:
|
||||
|
||||
#- name: print variables
|
||||
# debug:
|
||||
# msg: "Username: {{ account.username }}, Password: {{ account.password }}"
|
||||
|
||||
- name: Change password
|
||||
user:
|
||||
name: "{{ account.username }}"
|
||||
password: "{{ account.password | password_hash('des') }}"
|
||||
update_password: always
|
||||
when: account.password
|
||||
|
||||
- name: Change public key
|
||||
authorized_key:
|
||||
user: "{{ account.username }}"
|
||||
key: "{{ account.public_key }}"
|
||||
state: present
|
||||
when: account.public_key
|
||||
|
||||
- name: Verify password
|
||||
ping:
|
||||
vars:
|
||||
ansible_user: "{{ account.username }}"
|
||||
ansible_pass: "{{ account.password }}"
|
||||
ansible_ssh_connection: paramiko
|
|
@ -1,10 +0,0 @@
|
|||
{% for account in accounts %}
|
||||
- hosts: {{ account.asset.name }}
|
||||
vars:
|
||||
account:
|
||||
username: {{ account.username }}
|
||||
password: {{ account.password }}
|
||||
public_key: {{ account.public_key }}
|
||||
roles:
|
||||
- change_password
|
||||
{% endfor %}
|
|
@ -1,27 +0,0 @@
|
|||
- name: ping
|
||||
ping:
|
||||
|
||||
#- name: print variables
|
||||
# debug:
|
||||
# msg: "Username: {{ account.username }}, Password: {{ account.password }}"
|
||||
|
||||
- name: Change password
|
||||
user:
|
||||
name: "{{ account.username }}"
|
||||
password: "{{ account.password | password_hash('des') }}"
|
||||
update_password: always
|
||||
when: account.password
|
||||
|
||||
- name: Change public key
|
||||
authorized_key:
|
||||
user: "{{ account.username }}"
|
||||
key: "{{ account.public_key }}"
|
||||
state: present
|
||||
when: account.public_key
|
||||
|
||||
- name: Verify password
|
||||
ping:
|
||||
vars:
|
||||
ansible_user: "{{ account.username }}"
|
||||
ansible_pass: "{{ account.password }}"
|
||||
ansible_ssh_connection: paramiko
|
|
@ -1,8 +0,0 @@
|
|||
- hosts: all
|
||||
vars:
|
||||
account:
|
||||
username: {{ account.username }}
|
||||
password: {{ account.password }}
|
||||
public_key: {{ account.public_key }}
|
||||
roles:
|
||||
- change_password
|
|
@ -1,23 +0,0 @@
|
|||
- name: Check connection
|
||||
ping:
|
||||
|
||||
- name: Change password
|
||||
user:
|
||||
name: "{{ account.username }}"
|
||||
password: "{{ account.password | password_hash('sha512') }}"
|
||||
update_password: always
|
||||
when: account.password
|
||||
|
||||
- name: Change public key
|
||||
authorized_key:
|
||||
user: "{{ account.username }}"
|
||||
key: "{{ account.public_key }}"
|
||||
state: present
|
||||
when: account.public_key
|
||||
|
||||
- name: Verify password
|
||||
ping:
|
||||
vars:
|
||||
ansible_user: "{{ account.username }}"
|
||||
ansible_pass: "{{ account.password }}"
|
||||
ansible_ssh_connection: paramiko
|
|
@ -1,10 +0,0 @@
|
|||
{% for account in accounts %}
|
||||
- hosts: {{ account.asset.name }}
|
||||
vars:
|
||||
account:
|
||||
username: {{ account.username }}
|
||||
password: {{ account.password }}
|
||||
public_key: {{ account.public_key }}
|
||||
roles:
|
||||
- change_password
|
||||
{% endfor %}
|
|
@ -1,27 +0,0 @@
|
|||
- name: ping
|
||||
ping:
|
||||
|
||||
#- name: print variables
|
||||
# debug:
|
||||
# msg: "Username: {{ account.username }}, Password: {{ account.password }}"
|
||||
|
||||
- name: Change password
|
||||
user:
|
||||
name: "{{ account.username }}"
|
||||
password: "{{ account.password | password_hash('des') }}"
|
||||
update_password: always
|
||||
when: account.password
|
||||
|
||||
- name: Change public key
|
||||
authorized_key:
|
||||
user: "{{ account.username }}"
|
||||
key: "{{ account.public_key }}"
|
||||
state: present
|
||||
when: account.public_key
|
||||
|
||||
- name: Verify password
|
||||
ping:
|
||||
vars:
|
||||
ansible_user: "{{ account.username }}"
|
||||
ansible_pass: "{{ account.password }}"
|
||||
ansible_ssh_connection: paramiko
|
|
@ -1,13 +0,0 @@
|
|||
- hosts: centos
|
||||
gather_facts: no
|
||||
vars:
|
||||
account:
|
||||
username: web
|
||||
password: test123
|
||||
|
||||
tasks:
|
||||
- name: Verify password
|
||||
ping:
|
||||
vars:
|
||||
ansible_user: "{{ account.username }}"
|
||||
ansible_pass: "{{ account.password }}"
|
|
@ -1,10 +0,0 @@
|
|||
id: ansible_posix_ping
|
||||
name: Ansible posix ping
|
||||
description: Ansible ping
|
||||
category: host
|
||||
type:
|
||||
- linux
|
||||
- unix
|
||||
- macos
|
||||
- bsd
|
||||
method: verify_account
|
|
@ -1,13 +0,0 @@
|
|||
- hosts: centos
|
||||
gather_facts: no
|
||||
vars:
|
||||
account:
|
||||
username: web
|
||||
password: test123
|
||||
|
||||
tasks:
|
||||
- name: Verify password
|
||||
win_ping:
|
||||
vars:
|
||||
ansible_user: "{{ account.username }}"
|
||||
ansible_pass: "{{ account.password }}"
|
|
@ -1,6 +0,0 @@
|
|||
id: ansible_win_ping
|
||||
name: Ansible win ping
|
||||
category: host
|
||||
type:
|
||||
- windows
|
||||
method: verify_account
|
|
@ -1,12 +0,0 @@
|
|||
- hosts: all
|
||||
vars:
|
||||
connection_type: ssh
|
||||
password:
|
||||
value: {{ password }}
|
||||
public_key:
|
||||
value: {{ jms_key }}
|
||||
exclusive: {{ exclusive }}
|
||||
key_strategy: {{ key_strategy }}
|
||||
private_key_file: {{ private_key_file }}
|
||||
roles:
|
||||
- linux
|
|
@ -1,36 +0,0 @@
|
|||
- name: Check connection
|
||||
ping:
|
||||
|
||||
- name: Change password
|
||||
user:
|
||||
name: "{{ item }}"
|
||||
password: "{{ password.value | password_hash('sha512') }}"
|
||||
update_password: always
|
||||
with_items: "{{ usernames }}"
|
||||
when: "{{ password.value }}"
|
||||
|
||||
- name: Change public key
|
||||
authorized_key:
|
||||
user: "{{ item }}"
|
||||
key: "{{ lookup('file', id_rsa.pub) }}"
|
||||
state: present
|
||||
exclusive: "{{ public_key.exclusive }}"
|
||||
with_items: "{{ usernames }}"
|
||||
when: "{{ public_key.value and key_strategy != 'set_jms' }}"
|
||||
|
||||
- name: Change public key
|
||||
lineinfile:
|
||||
user: "{{ item }}"
|
||||
dest: /home/{{ item }}/.ssh/authorized_keys regexp='.*{{ public_key.value }}$
|
||||
state: absent
|
||||
with_items: "{{ usernames }}"
|
||||
when: "{{ public_key.value and key_strategy == 'set_jms' }}"
|
||||
|
||||
- name: Verify user
|
||||
ping:
|
||||
vars:
|
||||
ansible_user: "{{ item }}"
|
||||
ansible_pass: "{{ password.value }}"
|
||||
ansible_ssh_private_key_file: "{{ private_key_file }}"
|
||||
ansible_connection: "{{ connection_type | default('ssh') }}"
|
||||
with_items: "{{ usernames }}"
|
|
@ -1,5 +0,0 @@
|
|||
- hosts: all
|
||||
vars:
|
||||
connection_type: ssh
|
||||
roles:
|
||||
- linux
|
|
@ -1,8 +0,0 @@
|
|||
- name: Verify user
|
||||
ping:
|
||||
vars:
|
||||
ansible_user: "{{ item.username }}"
|
||||
ansible_pass: "{{ item.username }}"
|
||||
ansible_connection: "{{ connection_type | default('ssh') }}"
|
||||
ansible_ssh_private_key_file: "{{ item.private_key_file }}"
|
||||
with_items: "{{ account_info }}"
|
|
@ -1 +0,0 @@
|
|||
from .endpoint import *
|
|
@ -1,10 +0,0 @@
|
|||
from .backup.manager import AccountBackupExecutionManager
|
||||
|
||||
|
||||
class ExecutionManager:
|
||||
manager_type = {
|
||||
'backup': AccountBackupExecutionManager
|
||||
}
|
||||
|
||||
def __new__(cls, execution):
|
||||
return AccountBackupExecutionManager(execution)
|
|
@ -71,7 +71,7 @@ class DefaultCallback:
|
|||
def runner_on_start(self, event_data, **kwargs):
|
||||
pass
|
||||
|
||||
def runer_retry(self, event_data, **kwargs):
|
||||
def runner_retry(self, event_data, **kwargs):
|
||||
pass
|
||||
|
||||
def runner_on_file_diff(self, event_data, **kwargs):
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# ~*~ coding: utf-8 ~*~
|
||||
from collections import defaultdict
|
||||
import json
|
||||
import os
|
||||
from collections import defaultdict
|
||||
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
|
@ -10,7 +10,7 @@ __all__ = ['JMSInventory']
|
|||
|
||||
|
||||
class JMSInventory:
|
||||
def __init__(self, assets, account='', account_policy='smart', host_var_callback=None):
|
||||
def __init__(self, assets, account='', account_policy='smart', host_var_callback=None, host_duplicator=None):
|
||||
"""
|
||||
:param assets:
|
||||
:param account: account username name if not set use account_policy
|
||||
|
@ -21,6 +21,7 @@ class JMSInventory:
|
|||
self.account_username = account
|
||||
self.account_policy = account_policy
|
||||
self.host_var_callback = host_var_callback
|
||||
self.host_duplicator = host_duplicator
|
||||
|
||||
@staticmethod
|
||||
def clean_assets(assets):
|
||||
|
@ -59,11 +60,16 @@ class JMSInventory:
|
|||
return {"ansible_ssh_common_args": proxy_command}
|
||||
|
||||
def asset_to_host(self, asset, account, automation, protocols):
|
||||
host = {'name': asset.name, 'vars': {
|
||||
'asset_id': str(asset.id), 'asset_name': asset.name,
|
||||
'asset_type': asset.type, 'asset_category': asset.category,
|
||||
host = {
|
||||
'name': asset.name,
|
||||
'asset': {
|
||||
'id': asset.id, 'name': asset.name, 'ip': asset.ip,
|
||||
'type': asset.type, 'category': asset.category,
|
||||
'protocol': asset.protocol, 'port': asset.port,
|
||||
'protocols': [{'name': p.name, 'port': p.port} for p in protocols],
|
||||
},
|
||||
'exclude': ''
|
||||
}}
|
||||
}
|
||||
ansible_connection = automation.ansible_config.get('ansible_connection', 'ssh')
|
||||
gateway = None
|
||||
if asset.domain:
|
||||
|
@ -91,15 +97,15 @@ class JMSInventory:
|
|||
elif account.secret_type == 'private_key' and account.secret:
|
||||
host['ssh_private_key'] = account.private_key_file
|
||||
else:
|
||||
host['vars']['exclude'] = _("No account found")
|
||||
host['exclude'] = _("No account found")
|
||||
|
||||
if gateway:
|
||||
host['vars'].update(self.make_proxy_command(gateway))
|
||||
host.update(self.make_proxy_command(gateway))
|
||||
|
||||
if self.host_var_callback:
|
||||
callback_var = self.host_var_callback(asset)
|
||||
if isinstance(callback_var, dict):
|
||||
host['vars'].update(callback_var)
|
||||
host.update(callback_var)
|
||||
return host
|
||||
|
||||
def select_account(self, asset):
|
||||
|
@ -137,8 +143,11 @@ class JMSInventory:
|
|||
account = self.select_account(asset)
|
||||
host = self.asset_to_host(asset, account, automation, protocols)
|
||||
if not automation.ansible_enabled:
|
||||
host['vars']['exclude'] = _('Ansible disabled')
|
||||
hosts.append(host)
|
||||
host['exclude'] = _('Ansible disabled')
|
||||
if self.host_duplicator:
|
||||
hosts.extend(self.host_duplicator(host, asset=asset, account=account, platform=platform))
|
||||
else:
|
||||
hosts.append(host)
|
||||
|
||||
exclude_hosts = list(filter(lambda x: x.get('exclude'), hosts))
|
||||
if exclude_hosts:
|
||||
|
@ -150,8 +159,6 @@ class JMSInventory:
|
|||
data = {'all': {'hosts': {}}}
|
||||
for host in hosts:
|
||||
name = host.pop('name')
|
||||
var = host.pop('vars', {})
|
||||
host.update(var)
|
||||
data['all']['hosts'][name] = host
|
||||
return data
|
||||
|
||||
|
|
|
@ -52,7 +52,7 @@ class BaseAnsibleExecution(models.Model):
|
|||
creator = models.ForeignKey('users.User', verbose_name=_("Creator"), on_delete=models.SET_NULL, null=True)
|
||||
date_created = models.DateTimeField(auto_now_add=True, verbose_name=_('Date created'))
|
||||
date_start = models.DateTimeField(null=True, verbose_name=_('Date start'), db_index=True)
|
||||
date_finished = models.DateTimeField(null=True)
|
||||
date_finished = models.DateTimeField(null=True, verbose_name=_("Date finished"))
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
from .endpoint import *
|
|
@ -1,2 +0,0 @@
|
|||
from .manager import *
|
||||
from .handlers import *
|
|
@ -1,16 +0,0 @@
|
|||
"""
|
||||
执行改密计划的基类
|
||||
"""
|
||||
from common.utils import get_logger
|
||||
|
||||
logger = get_logger(__file__)
|
||||
|
||||
|
||||
class BaseHandler:
|
||||
def __init__(self, task, show_step_info=True):
|
||||
self.task = task
|
||||
self.conn = None
|
||||
self.retry_times = 3
|
||||
self.current_step = 0
|
||||
self.is_frozen = False # 任务状态冻结标志
|
||||
self.show_step_info = show_step_info
|
|
@ -1,78 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
import time
|
||||
from openpyxl import Workbook
|
||||
from django.utils import timezone
|
||||
|
||||
from common.utils import get_logger
|
||||
from common.utils.timezone import local_now_display
|
||||
|
||||
logger = get_logger(__file__)
|
||||
|
||||
|
||||
class BaseExecutionManager:
|
||||
task_back_up_serializer: None
|
||||
|
||||
def __init__(self, execution):
|
||||
self.execution = execution
|
||||
self.date_start = timezone.now()
|
||||
self.time_start = time.time()
|
||||
self.date_end = None
|
||||
self.time_end = None
|
||||
self.timedelta = 0
|
||||
self.total_tasks = []
|
||||
|
||||
def on_tasks_pre_run(self, tasks):
|
||||
raise NotImplementedError
|
||||
|
||||
def on_per_task_pre_run(self, task, total, index):
|
||||
raise NotImplementedError
|
||||
|
||||
def create_csv_file(self, tasks, file_name):
|
||||
raise NotImplementedError
|
||||
|
||||
def get_handler_cls(self):
|
||||
raise NotImplemented
|
||||
|
||||
def do_run(self):
|
||||
tasks = self.total_tasks = self.execution.create_plan_tasks()
|
||||
self.on_tasks_pre_run(tasks)
|
||||
total = len(tasks)
|
||||
|
||||
for index, task in enumerate(tasks, start=1):
|
||||
self.on_per_task_pre_run(task, total, index)
|
||||
task.start(show_step_info=False)
|
||||
|
||||
def pre_run(self):
|
||||
self.execution.date_start = self.date_start
|
||||
self.execution.save()
|
||||
self.show_execution_steps()
|
||||
|
||||
def show_execution_steps(self):
|
||||
pass
|
||||
|
||||
def show_summary(self):
|
||||
split_line = '#' * 40
|
||||
summary = self.execution.result_summary
|
||||
logger.info(f'\n{split_line} 改密计划执行结果汇总 {split_line}')
|
||||
logger.info(
|
||||
'\n成功: {succeed}, 失败: {failed}, 总数: {total}\n'
|
||||
''.format(**summary)
|
||||
)
|
||||
|
||||
def post_run(self):
|
||||
self.time_end = time.time()
|
||||
self.date_end = timezone.now()
|
||||
|
||||
logger.info('\n\n' + '-' * 80)
|
||||
logger.info('任务执行结束 {}\n'.format(local_now_display()))
|
||||
self.timedelta = int(self.time_end - self.time_start)
|
||||
logger.info('用时: {}s'.format(self.timedelta))
|
||||
self.execution.timedelta = self.timedelta
|
||||
self.execution.save()
|
||||
self.show_summary()
|
||||
|
||||
def run(self):
|
||||
self.pre_run()
|
||||
self.do_run()
|
||||
self.post_run()
|
|
@ -1,2 +0,0 @@
|
|||
from .manager import *
|
||||
from .handlers import *
|
|
@ -1,10 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from common.utils import get_logger
|
||||
from ..base import BaseHandler
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
class ChangeAuthHandler(BaseHandler):
|
||||
pass
|
|
@ -1,12 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from common.utils import get_logger
|
||||
from ..base import BaseExecutionManager
|
||||
from .handlers import ChangeAuthHandler
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
class ChangeAuthExecutionManager(BaseExecutionManager):
|
||||
def get_handler_cls(self):
|
||||
return ChangeAuthHandler
|
|
@ -1,2 +0,0 @@
|
|||
from .manager import *
|
||||
from .handlers import *
|
|
@ -1,10 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from common.utils import get_logger
|
||||
from ..base import BaseHandler
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
class CollectHandler(BaseHandler):
|
||||
pass
|
|
@ -1,10 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from common.utils import get_logger
|
||||
from ..base import BaseExecutionManager
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
class CollectExecutionManager(object):
|
||||
pass
|
|
@ -1,31 +0,0 @@
|
|||
from ops.const import StrategyChoice
|
||||
from .push import PushExecutionManager, PushHandler
|
||||
from .verify import VerifyExecutionManager, VerifyHandler
|
||||
from .collect import CollectExecutionManager, CollectHandler
|
||||
from .change_auth import ChangeAuthExecutionManager, ChangeAuthHandler
|
||||
|
||||
|
||||
class ExecutionManager:
|
||||
manager_type = {
|
||||
StrategyChoice.push: PushExecutionManager,
|
||||
StrategyChoice.verify: VerifyExecutionManager,
|
||||
StrategyChoice.collect: CollectExecutionManager,
|
||||
StrategyChoice.change_password: ChangeAuthExecutionManager,
|
||||
}
|
||||
|
||||
def __new__(cls, execution):
|
||||
manager = cls.manager_type[execution.manager_type]
|
||||
return manager(execution)
|
||||
|
||||
|
||||
class TaskHandler:
|
||||
handler_type = {
|
||||
StrategyChoice.push: PushHandler,
|
||||
StrategyChoice.verify: VerifyHandler,
|
||||
StrategyChoice.collect: CollectHandler,
|
||||
StrategyChoice.change_password: ChangeAuthHandler,
|
||||
}
|
||||
|
||||
def __new__(cls, task, show_step_info):
|
||||
handler = cls.handler_type[task.handler_type]
|
||||
return handler(task, show_step_info)
|
|
@ -1,2 +0,0 @@
|
|||
from .manager import *
|
||||
from .handlers import *
|
|
@ -1,10 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from common.utils import get_logger
|
||||
from ..base import BaseHandler
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
class PushHandler(BaseHandler):
|
||||
pass
|
|
@ -1,10 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from common.utils import get_logger
|
||||
from ..base import BaseExecutionManager
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
class PushExecutionManager(BaseExecutionManager):
|
||||
pass
|
|
@ -1,2 +0,0 @@
|
|||
from .manager import *
|
||||
from .handlers import *
|
|
@ -1,10 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from common.utils import get_logger
|
||||
from ..base import BaseHandler
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
class VerifyHandler(BaseHandler):
|
||||
pass
|
|
@ -1,10 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from common.utils import get_logger
|
||||
from ..base import BaseExecutionManager
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
class VerifyExecutionManager(BaseExecutionManager):
|
||||
pass
|
|
@ -8,7 +8,7 @@ from common.utils import get_logger, get_object_or_none
|
|||
from orgs.utils import org_aware_func
|
||||
from jumpserver.const import PROJECT_DIR
|
||||
|
||||
from .models import Task, AdHoc
|
||||
from .models import AdHoc
|
||||
from .const import DEFAULT_PASSWORD_RULES
|
||||
|
||||
logger = get_logger(__file__)
|
||||
|
|
Loading…
Reference in New Issue