mirror of https://github.com/jumpserver/jumpserver
Merge branch 'v3' of github.com:jumpserver/jumpserver into v3
commit
a5244ee68f
|
@ -43,7 +43,7 @@ class AccountSecretsViewSet(RecordViewLogMixin, AccountViewSet):
|
||||||
serializer_classes = {
|
serializer_classes = {
|
||||||
'default': serializers.AccountSecretSerializer
|
'default': serializers.AccountSecretSerializer
|
||||||
}
|
}
|
||||||
http_method_names = ['get']
|
http_method_names = ['get', 'options']
|
||||||
# Todo: 记得打开
|
# Todo: 记得打开
|
||||||
# permission_classes = [RBACPermission, UserConfirmation.require(ConfirmType.MFA)]
|
# permission_classes = [RBACPermission, UserConfirmation.require(ConfirmType.MFA)]
|
||||||
rbac_perms = {
|
rbac_perms = {
|
||||||
|
|
|
@ -1,19 +1,68 @@
|
||||||
import os
|
import os
|
||||||
import shutil
|
|
||||||
import yaml
|
import yaml
|
||||||
|
import shutil
|
||||||
|
from hashlib import md5
|
||||||
|
from copy import deepcopy
|
||||||
|
from socket import gethostname
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
from django.db.models import Model
|
||||||
from django.utils.translation import gettext as _
|
from django.utils.translation import gettext as _
|
||||||
|
|
||||||
from common.utils import get_logger
|
from common.utils import get_logger
|
||||||
|
from common.utils import ssh_pubkey_gen, ssh_key_string_to_obj
|
||||||
|
from assets.const import SecretType
|
||||||
from assets.automations.methods import platform_automation_methods
|
from assets.automations.methods import platform_automation_methods
|
||||||
from ops.ansible import JMSInventory, PlaybookRunner, DefaultCallback
|
from ops.ansible import JMSInventory, PlaybookRunner, DefaultCallback
|
||||||
|
|
||||||
logger = get_logger(__name__)
|
logger = get_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class PushOrVerifyHostCallbackMixin:
|
||||||
|
execution: callable
|
||||||
|
host_account_mapper: dict
|
||||||
|
ignore_account: bool
|
||||||
|
generate_public_key: callable
|
||||||
|
generate_private_key_path: callable
|
||||||
|
|
||||||
|
def host_callback(self, host, asset=None, account=None, automation=None, path_dir=None, **kwargs):
|
||||||
|
host = super().host_callback(host, asset=asset, account=account, automation=automation, **kwargs)
|
||||||
|
if host.get('error'):
|
||||||
|
return host
|
||||||
|
|
||||||
|
accounts = asset.accounts.all()
|
||||||
|
if self.ignore_account and account:
|
||||||
|
accounts = accounts.exclude(id=account.id)
|
||||||
|
|
||||||
|
if '*' not in self.execution.snapshot['accounts']:
|
||||||
|
accounts = accounts.filter(username__in=self.execution.snapshot['accounts'])
|
||||||
|
|
||||||
|
inventory_hosts = []
|
||||||
|
for account in accounts:
|
||||||
|
h = deepcopy(host)
|
||||||
|
h['name'] += '_' + account.username
|
||||||
|
self.host_account_mapper[h['name']] = account
|
||||||
|
secret = account.secret
|
||||||
|
|
||||||
|
private_key_path = None
|
||||||
|
if account.secret_type == SecretType.ssh_key:
|
||||||
|
private_key_path = self.generate_private_key_path(secret, path_dir)
|
||||||
|
secret = self.generate_public_key(secret)
|
||||||
|
|
||||||
|
h['secret_type'] = account.secret_type
|
||||||
|
h['account'] = {
|
||||||
|
'name': account.name,
|
||||||
|
'username': account.username,
|
||||||
|
'secret_type': account.secret_type,
|
||||||
|
'secret': secret,
|
||||||
|
'private_key_path': private_key_path
|
||||||
|
}
|
||||||
|
inventory_hosts.append(h)
|
||||||
|
return inventory_hosts
|
||||||
|
|
||||||
|
|
||||||
class PlaybookCallback(DefaultCallback):
|
class PlaybookCallback(DefaultCallback):
|
||||||
def playbook_on_stats(self, event_data, **kwargs):
|
def playbook_on_stats(self, event_data, **kwargs):
|
||||||
super().playbook_on_stats(event_data, **kwargs)
|
super().playbook_on_stats(event_data, **kwargs)
|
||||||
|
@ -66,20 +115,33 @@ class BasePlaybookManager:
|
||||||
method_attr = '{}_method'.format(self.__class__.method_type())
|
method_attr = '{}_method'.format(self.__class__.method_type())
|
||||||
|
|
||||||
method_enabled = automation and \
|
method_enabled = automation and \
|
||||||
getattr(automation, enabled_attr) and \
|
getattr(automation, enabled_attr) and \
|
||||||
getattr(automation, method_attr) and \
|
getattr(automation, method_attr) and \
|
||||||
getattr(automation, method_attr) in self.method_id_meta_mapper
|
getattr(automation, method_attr) in self.method_id_meta_mapper
|
||||||
|
|
||||||
if not method_enabled:
|
if not method_enabled:
|
||||||
host['error'] = _('{} disabled'.format(self.__class__.method_type()))
|
host['error'] = _('{} disabled'.format(self.__class__.method_type()))
|
||||||
return host
|
return host
|
||||||
return host
|
return host
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def generate_public_key(private_key):
|
||||||
|
return ssh_pubkey_gen(private_key=private_key, hostname=gethostname())
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def generate_private_key_path(secret, path_dir):
|
||||||
|
key_name = '.' + md5(secret.encode('utf-8')).hexdigest()
|
||||||
|
key_path = os.path.join(path_dir, key_name)
|
||||||
|
if not os.path.exists(key_path):
|
||||||
|
ssh_key_string_to_obj(secret, password=None).write_private_key_file(key_path)
|
||||||
|
os.chmod(key_path, 0o400)
|
||||||
|
return key_path
|
||||||
|
|
||||||
def generate_inventory(self, platformed_assets, inventory_path):
|
def generate_inventory(self, platformed_assets, inventory_path):
|
||||||
inventory = JMSInventory(
|
inventory = JMSInventory(
|
||||||
manager=self,
|
|
||||||
assets=platformed_assets,
|
assets=platformed_assets,
|
||||||
account_policy=self.ansible_account_policy,
|
account_policy=self.ansible_account_policy,
|
||||||
|
host_callback=self.host_callback,
|
||||||
)
|
)
|
||||||
inventory.write_to_file(inventory_path)
|
inventory.write_to_file(inventory_path)
|
||||||
|
|
||||||
|
@ -105,7 +167,7 @@ class BasePlaybookManager:
|
||||||
def get_runners(self):
|
def get_runners(self):
|
||||||
runners = []
|
runners = []
|
||||||
for platform, assets in self.get_assets_group_by_platform().items():
|
for platform, assets in self.get_assets_group_by_platform().items():
|
||||||
assets_bulked = [assets[i:i+self.bulk_size] for i in range(0, len(assets), self.bulk_size)]
|
assets_bulked = [assets[i:i + self.bulk_size] for i in range(0, len(assets), self.bulk_size)]
|
||||||
|
|
||||||
for i, _assets in enumerate(assets_bulked, start=1):
|
for i, _assets in enumerate(assets_bulked, start=1):
|
||||||
sub_dir = '{}_{}'.format(platform.name, i)
|
sub_dir = '{}_{}'.format(platform.name, i)
|
||||||
|
@ -148,7 +210,7 @@ class BasePlaybookManager:
|
||||||
print(" inventory: {}".format(runner.inventory))
|
print(" inventory: {}".format(runner.inventory))
|
||||||
print(" playbook: {}".format(runner.playbook))
|
print(" playbook: {}".format(runner.playbook))
|
||||||
|
|
||||||
def run(self, *args, **kwargs):
|
def run(self, *args, **kwargs):
|
||||||
runners = self.get_runners()
|
runners = self.get_runners()
|
||||||
if len(runners) > 1:
|
if len(runners) > 1:
|
||||||
print("### 分批次执行开始任务, 总共 {}\n".format(len(runners)))
|
print("### 分批次执行开始任务, 总共 {}\n".format(len(runners)))
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
login_password: "{{ jms_account.secret }}"
|
login_password: "{{ jms_account.secret }}"
|
||||||
login_host: "{{ jms_asset.address }}"
|
login_host: "{{ jms_asset.address }}"
|
||||||
login_port: "{{ jms_asset.port }}"
|
login_port: "{{ jms_asset.port }}"
|
||||||
login_db: "{{ jms_asset.database }}"
|
login_db: "{{ jms_asset.category_property.db_name }}"
|
||||||
register: db_info
|
register: db_info
|
||||||
|
|
||||||
- name: Display PostgreSQL version
|
- name: Display PostgreSQL version
|
||||||
|
@ -24,7 +24,7 @@
|
||||||
login_password: "{{ jms_account.secret }}"
|
login_password: "{{ jms_account.secret }}"
|
||||||
login_host: "{{ jms_asset.address }}"
|
login_host: "{{ jms_asset.address }}"
|
||||||
login_port: "{{ jms_asset.port }}"
|
login_port: "{{ jms_asset.port }}"
|
||||||
db: "{{ jms_asset.database }}"
|
db: "{{ jms_asset.category_property.db_name }}"
|
||||||
name: "{{ account.username }}"
|
name: "{{ account.username }}"
|
||||||
password: "{{ account.secret }}"
|
password: "{{ account.secret }}"
|
||||||
when: db_info is succeeded
|
when: db_info is succeeded
|
||||||
|
@ -36,7 +36,7 @@
|
||||||
login_password: "{{ account.secret }}"
|
login_password: "{{ account.secret }}"
|
||||||
login_host: "{{ jms_asset.address }}"
|
login_host: "{{ jms_asset.address }}"
|
||||||
login_port: "{{ jms_asset.port }}"
|
login_port: "{{ jms_asset.port }}"
|
||||||
db: "{{ jms_asset.database }}"
|
db: "{{ jms_asset.category_property.db_name }}"
|
||||||
when:
|
when:
|
||||||
- db_info is succeeded
|
- db_info is succeeded
|
||||||
- change_info is succeeded
|
- change_info is succeeded
|
||||||
|
|
|
@ -1,58 +0,0 @@
|
||||||
- hosts: demo
|
|
||||||
gather_facts: no
|
|
||||||
tasks:
|
|
||||||
- name: Test privileged account
|
|
||||||
ansible.builtin.ping:
|
|
||||||
#
|
|
||||||
# - name: print variables
|
|
||||||
# debug:
|
|
||||||
# msg: "Username: {{ account.username }}, Secret: {{ account.secret }}, Secret type: {{ secret_type }}"
|
|
||||||
|
|
||||||
- name: Change password
|
|
||||||
ansible.builtin.user:
|
|
||||||
name: "{{ account.username }}"
|
|
||||||
password: "{{ account.secret | password_hash('sha512') }}"
|
|
||||||
update_password: always
|
|
||||||
when: secret_type == "password"
|
|
||||||
|
|
||||||
- name: create user If it already exists, no operation will be performed
|
|
||||||
ansible.builtin.user:
|
|
||||||
name: "{{ account.username }}"
|
|
||||||
when: secret_type == "ssh_key"
|
|
||||||
|
|
||||||
- name: remove jumpserver ssh key
|
|
||||||
ansible.builtin.lineinfile:
|
|
||||||
dest: "{{ kwargs.dest }}"
|
|
||||||
regexp: "{{ kwargs.regexp }}"
|
|
||||||
state: absent
|
|
||||||
when:
|
|
||||||
- secret_type == "ssh_key"
|
|
||||||
- kwargs.strategy == "set_jms"
|
|
||||||
|
|
||||||
- name: Change SSH key
|
|
||||||
ansible.builtin.authorized_key:
|
|
||||||
user: "{{ account.username }}"
|
|
||||||
key: "{{ account.secret }}"
|
|
||||||
exclusive: "{{ kwargs.exclusive }}"
|
|
||||||
when: secret_type == "ssh_key"
|
|
||||||
|
|
||||||
- name: Refresh connection
|
|
||||||
ansible.builtin.meta: reset_connection
|
|
||||||
|
|
||||||
- name: Verify password
|
|
||||||
ansible.builtin.ping:
|
|
||||||
become: no
|
|
||||||
vars:
|
|
||||||
ansible_user: "{{ account.username }}"
|
|
||||||
ansible_password: "{{ account.secret }}"
|
|
||||||
ansible_become: no
|
|
||||||
when: secret_type == "password"
|
|
||||||
|
|
||||||
- name: Verify SSH key
|
|
||||||
ansible.builtin.ping:
|
|
||||||
become: no
|
|
||||||
vars:
|
|
||||||
ansible_user: "{{ account.username }}"
|
|
||||||
ansible_ssh_private_key_file: "{{ account.private_key_path }}"
|
|
||||||
ansible_become: no
|
|
||||||
when: secret_type == "ssh_key"
|
|
|
@ -1,6 +0,0 @@
|
||||||
id: change_secret_aix
|
|
||||||
name: Change password for AIX
|
|
||||||
category: host
|
|
||||||
type:
|
|
||||||
- aix
|
|
||||||
method: change_secret
|
|
|
@ -1,14 +1,11 @@
|
||||||
import os
|
|
||||||
import random
|
import random
|
||||||
import string
|
import string
|
||||||
from hashlib import md5
|
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
from socket import gethostname
|
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
|
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
|
||||||
from common.utils import lazyproperty, gen_key_pair, ssh_pubkey_gen, ssh_key_string_to_obj
|
from common.utils import lazyproperty, gen_key_pair
|
||||||
from assets.models import ChangeSecretRecord
|
from assets.models import ChangeSecretRecord
|
||||||
from assets.const import (
|
from assets.const import (
|
||||||
AutomationTypes, SecretType, SecretStrategy, SSHKeyStrategy, DEFAULT_PASSWORD_RULES
|
AutomationTypes, SecretType, SecretStrategy, SSHKeyStrategy, DEFAULT_PASSWORD_RULES
|
||||||
|
@ -39,19 +36,6 @@ class ChangeSecretManager(BasePlaybookManager):
|
||||||
private_key, public_key = gen_key_pair()
|
private_key, public_key = gen_key_pair()
|
||||||
return private_key
|
return private_key
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def generate_public_key(private_key):
|
|
||||||
return ssh_pubkey_gen(private_key=private_key, hostname=gethostname())
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def generate_private_key_path(secret, path_dir):
|
|
||||||
key_name = '.' + md5(secret.encode('utf-8')).hexdigest()
|
|
||||||
key_path = os.path.join(path_dir, key_name)
|
|
||||||
if not os.path.exists(key_path):
|
|
||||||
ssh_key_string_to_obj(secret, password=None).write_private_key_file(key_path)
|
|
||||||
os.chmod(key_path, 0o400)
|
|
||||||
return key_path
|
|
||||||
|
|
||||||
def generate_password(self):
|
def generate_password(self):
|
||||||
kwargs = self.execution.snapshot['password_rules'] or {}
|
kwargs = self.execution.snapshot['password_rules'] or {}
|
||||||
length = int(kwargs.get('length', DEFAULT_PASSWORD_RULES['length']))
|
length = int(kwargs.get('length', DEFAULT_PASSWORD_RULES['length']))
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
from .change_secret.manager import ChangeSecretManager
|
from .change_secret.manager import ChangeSecretManager
|
||||||
from .gather_facts.manager import GatherFactsManager
|
from .gather_facts.manager import GatherFactsManager
|
||||||
from .gather_accounts.manager import GatherAccountsManager
|
from .gather_accounts.manager import GatherAccountsManager
|
||||||
|
from .verify_account.manager import VerifyAccountManager
|
||||||
|
from .push_account.manager import PushAccountManager
|
||||||
from ..const import AutomationTypes
|
from ..const import AutomationTypes
|
||||||
|
|
||||||
|
|
||||||
|
@ -9,6 +11,8 @@ class ExecutionManager:
|
||||||
AutomationTypes.change_secret: ChangeSecretManager,
|
AutomationTypes.change_secret: ChangeSecretManager,
|
||||||
AutomationTypes.gather_facts: GatherFactsManager,
|
AutomationTypes.gather_facts: GatherFactsManager,
|
||||||
AutomationTypes.gather_accounts: GatherAccountsManager,
|
AutomationTypes.gather_accounts: GatherAccountsManager,
|
||||||
|
AutomationTypes.verify_account: VerifyAccountManager,
|
||||||
|
AutomationTypes.push_account: PushAccountManager,
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self, execution):
|
def __init__(self, execution):
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
- hosts: mysql
|
- hosts: mysql
|
||||||
gather_facts: no
|
gather_facts: no
|
||||||
vars:
|
vars:
|
||||||
ansible_python_interpreter: /usr/local/bin/python
|
ansible_python_interpreter: /Users/xiaofeng/Desktop/jumpserver/venv/bin/python
|
||||||
|
|
||||||
tasks:
|
tasks:
|
||||||
- name: Get info
|
- name: Get info
|
||||||
|
@ -9,7 +9,7 @@
|
||||||
login_user: "{{ jms_account.username }}"
|
login_user: "{{ jms_account.username }}"
|
||||||
login_password: "{{ jms_account.secret }}"
|
login_password: "{{ jms_account.secret }}"
|
||||||
login_host: "{{ jms_asset.address }}"
|
login_host: "{{ jms_asset.address }}"
|
||||||
login_port: "{{ jms_asset.port }}"
|
login_port: 1234
|
||||||
filter: users
|
filter: users
|
||||||
register: db_info
|
register: db_info
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
login_password: "{{ jms_account.secret }}"
|
login_password: "{{ jms_account.secret }}"
|
||||||
login_host: "{{ jms_asset.address }}"
|
login_host: "{{ jms_asset.address }}"
|
||||||
login_port: "{{ jms_asset.port }}"
|
login_port: "{{ jms_asset.port }}"
|
||||||
login_db: "{{ jms_asset.database }}"
|
login_db: "{{ jms_asset.category_property.db_name }}"
|
||||||
filter: "roles"
|
filter: "roles"
|
||||||
register: db_info
|
register: db_info
|
||||||
|
|
||||||
|
|
|
@ -39,8 +39,11 @@ class GatherAccountsFilter:
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def windows_filter(info):
|
def windows_filter(info):
|
||||||
# TODO
|
info = info[4:-2]
|
||||||
result = {}
|
result = {}
|
||||||
|
for i in info:
|
||||||
|
for username in i.split():
|
||||||
|
result[username] = {}
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def run(self, method_id_meta_mapper, info):
|
def run(self, method_id_meta_mapper, info):
|
||||||
|
|
|
@ -2,8 +2,10 @@
|
||||||
gather_facts: no
|
gather_facts: no
|
||||||
tasks:
|
tasks:
|
||||||
- name: Gather posix account
|
- name: Gather posix account
|
||||||
ansible.builtin.win_shell:
|
ansible.builtin.shell:
|
||||||
cmd: net user
|
cmd: >
|
||||||
|
users=$(getent passwd | grep -v nologin | grep -v shutdown | awk -F":" '{ print $1 }');for i in $users;
|
||||||
|
do last -w -F $i -1 | head -1 | grep -v ^$ | awk '{ print $1"@"$3"@"$5,$6,$7,$8 }';done
|
||||||
register: result
|
register: result
|
||||||
|
|
||||||
- name: Define info by set_fact
|
- name: Define info by set_fact
|
||||||
|
|
|
@ -1,18 +1,14 @@
|
||||||
- hosts: windows
|
- hosts: demo
|
||||||
gather_facts: yes
|
gather_facts: no
|
||||||
tasks:
|
tasks:
|
||||||
- name: Get info
|
- name: Gather posix account
|
||||||
set_fact:
|
ansible.builtin.win_shell:
|
||||||
info:
|
cmd: net user
|
||||||
arch: "{{ ansible_architecture2 }}"
|
register: result
|
||||||
distribution: "{{ ansible_distribution }}"
|
|
||||||
distribution_version: "{{ ansible_distribution_version }}"
|
- name: Define info by set_fact
|
||||||
kernel: "{{ ansible_kernel }}"
|
ansible.builtin.set_fact:
|
||||||
vendor: "{{ ansible_system_vendor }}"
|
info: "{{ result.stdout_lines }}"
|
||||||
model: "{{ ansible_product_name }}"
|
|
||||||
sn: "{{ ansible_product_serial }}"
|
|
||||||
cpu_vcpus: "{{ ansible_processor_vcpus }}"
|
|
||||||
memory: "{{ ansible_memtotal_mb }}"
|
|
||||||
|
|
||||||
- debug:
|
- debug:
|
||||||
var: info
|
var: info
|
|
@ -2,7 +2,7 @@ from common.utils import get_logger
|
||||||
from assets.const import AutomationTypes
|
from assets.const import AutomationTypes
|
||||||
from orgs.utils import tmp_to_org
|
from orgs.utils import tmp_to_org
|
||||||
from .filter import GatherAccountsFilter
|
from .filter import GatherAccountsFilter
|
||||||
from ...models import Account, GatheredUser
|
from ...models import GatheredUser
|
||||||
from ..base.manager import BasePlaybookManager
|
from ..base.manager import BasePlaybookManager
|
||||||
|
|
||||||
logger = get_logger(__name__)
|
logger = get_logger(__name__)
|
||||||
|
@ -42,4 +42,4 @@ class GatherAccountsManager(BasePlaybookManager):
|
||||||
defaults['ip_last_login'] = data['address'][:32]
|
defaults['ip_last_login'] = data['address'][:32]
|
||||||
GatheredUser.objects.update_or_create(defaults=defaults, asset=asset, username=username)
|
GatheredUser.objects.update_or_create(defaults=defaults, asset=asset, username=username)
|
||||||
else:
|
else:
|
||||||
logger.error("Not found info, task name must be 'Get info': {}".format(host))
|
logger.error("Not found info".format(host))
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
login_password: "{{ jms_account.secret }}"
|
login_password: "{{ jms_account.secret }}"
|
||||||
login_host: "{{ jms_asset.address }}"
|
login_host: "{{ jms_asset.address }}"
|
||||||
login_port: "{{ jms_asset.port }}"
|
login_port: "{{ jms_asset.port }}"
|
||||||
login_db: "{{ jms_asset.database }}"
|
login_db: "{{ jms_asset.category_property.db_name }}"
|
||||||
register: db_info
|
register: db_info
|
||||||
|
|
||||||
- name: Define info by set_fact
|
- name: Define info by set_fact
|
||||||
|
|
|
@ -26,4 +26,4 @@ class GatherFactsManager(BasePlaybookManager):
|
||||||
asset.info = info
|
asset.info = info
|
||||||
asset.save()
|
asset.save()
|
||||||
else:
|
else:
|
||||||
logger.error("Not found info, task name must be 'Get info': {}".format(host))
|
logger.error("Not found info: {}".format(host))
|
||||||
|
|
|
@ -2,12 +2,6 @@
|
||||||
gather_facts: no
|
gather_facts: no
|
||||||
vars:
|
vars:
|
||||||
ansible_python_interpreter: /usr/local/bin/python
|
ansible_python_interpreter: /usr/local/bin/python
|
||||||
jms_account:
|
|
||||||
username: root
|
|
||||||
password: redhat
|
|
||||||
jms_asset:
|
|
||||||
address: 127.0.0.1
|
|
||||||
port: 3306
|
|
||||||
|
|
||||||
tasks:
|
tasks:
|
||||||
- name: Test MySQL connection
|
- name: Test MySQL connection
|
||||||
|
@ -17,4 +11,3 @@
|
||||||
login_host: "{{ jms_asset.address }}"
|
login_host: "{{ jms_asset.address }}"
|
||||||
login_port: "{{ jms_asset.port }}"
|
login_port: "{{ jms_asset.port }}"
|
||||||
filter: version
|
filter: version
|
||||||
register: db_info
|
|
||||||
|
|
|
@ -2,16 +2,6 @@
|
||||||
gather_facts: no
|
gather_facts: no
|
||||||
vars:
|
vars:
|
||||||
ansible_python_interpreter: /usr/local/bin/python
|
ansible_python_interpreter: /usr/local/bin/python
|
||||||
jms_account:
|
|
||||||
username: postgre
|
|
||||||
secret: postgre
|
|
||||||
jms_asset:
|
|
||||||
address: 127.0.0.1
|
|
||||||
port: 5432
|
|
||||||
database: testdb
|
|
||||||
account:
|
|
||||||
username: test
|
|
||||||
secret: jumpserver
|
|
||||||
|
|
||||||
tasks:
|
tasks:
|
||||||
- name: Test PostgreSQL connection
|
- name: Test PostgreSQL connection
|
||||||
|
@ -20,4 +10,4 @@
|
||||||
login_password: "{{ jms_account.secret }}"
|
login_password: "{{ jms_account.secret }}"
|
||||||
login_host: "{{ jms_asset.address }}"
|
login_host: "{{ jms_asset.address }}"
|
||||||
login_port: "{{ jms_asset.port }}"
|
login_port: "{{ jms_asset.port }}"
|
||||||
login_db: "{{ jms_asset.database }}"
|
login_db: "{{ jms_asset.category_property.db_name }}"
|
||||||
|
|
|
@ -2,4 +2,4 @@
|
||||||
gather_facts: no
|
gather_facts: no
|
||||||
tasks:
|
tasks:
|
||||||
- name: Windows ping
|
- name: Windows ping
|
||||||
win_ping:
|
ansible.builtin.win_ping:
|
||||||
|
|
|
@ -6,4 +6,15 @@ logger = get_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class PingManager(BasePlaybookManager):
|
class PingManager(BasePlaybookManager):
|
||||||
pass
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self.host_asset_mapper = {}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def method_type(cls):
|
||||||
|
return AutomationTypes.ping
|
||||||
|
|
||||||
|
def host_callback(self, host, asset=None, **kwargs):
|
||||||
|
super().host_callback(host, asset=asset, **kwargs)
|
||||||
|
self.host_asset_mapper[host['name']] = asset
|
||||||
|
return host
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
- hosts: mysql
|
||||||
|
gather_facts: no
|
||||||
|
vars:
|
||||||
|
ansible_python_interpreter: /usr/local/bin/python
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
- name: Add user account.username
|
||||||
|
community.mysql.mysql_user:
|
||||||
|
login_user: "{{ jms_account.username }}"
|
||||||
|
login_password: "{{ jms_account.secret }}"
|
||||||
|
login_host: "{{ jms_asset.address }}"
|
||||||
|
login_port: "{{ jms_asset.port }}"
|
||||||
|
name: "{{ account.username }}"
|
||||||
|
password: "{{ account.secret }}"
|
||||||
|
host: "%"
|
|
@ -0,0 +1,6 @@
|
||||||
|
id: push_account_mysql
|
||||||
|
name: Push account from MySQL
|
||||||
|
category: database
|
||||||
|
type:
|
||||||
|
- mysql
|
||||||
|
method: push_account
|
|
@ -0,0 +1,16 @@
|
||||||
|
- hosts: postgresql
|
||||||
|
gather_facts: no
|
||||||
|
vars:
|
||||||
|
ansible_python_interpreter: /usr/local/bin/python
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
- name: Add user account.username
|
||||||
|
community.postgresql.postgresql_user:
|
||||||
|
login_user: "{{ jms_account.username }}"
|
||||||
|
login_password: "{{ jms_account.secret }}"
|
||||||
|
login_host: "{{ jms_asset.address }}"
|
||||||
|
login_port: "{{ jms_asset.port }}"
|
||||||
|
db: "{{ jms_asset.category_property.db_name }}"
|
||||||
|
name: "{{ account.username }}"
|
||||||
|
password: "{{ account.secret }}"
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
id: push_account_postgresql
|
||||||
|
name: Push account for PostgreSQL
|
||||||
|
category: database
|
||||||
|
type:
|
||||||
|
- postgresql
|
||||||
|
method: push_account
|
|
@ -0,0 +1,19 @@
|
||||||
|
- hosts: demo
|
||||||
|
gather_facts: no
|
||||||
|
tasks:
|
||||||
|
- name: Add user account.username
|
||||||
|
ansible.builtin.user:
|
||||||
|
name: "{{ account.username }}"
|
||||||
|
|
||||||
|
- name: Set account.username password
|
||||||
|
ansible.builtin.user:
|
||||||
|
name: "{{ account.username }}"
|
||||||
|
password: "{{ account.secret | password_hash('sha512') }}"
|
||||||
|
update_password: always
|
||||||
|
when: secret_type == "password"
|
||||||
|
|
||||||
|
- name: Set account.username SSH key
|
||||||
|
ansible.builtin.authorized_key:
|
||||||
|
user: "{{ account.username }}"
|
||||||
|
key: "{{ account.secret }}"
|
||||||
|
when: secret_type == "ssh_key"
|
|
@ -0,0 +1,7 @@
|
||||||
|
id: push_account_posix
|
||||||
|
name: Push posix account
|
||||||
|
category: host
|
||||||
|
type:
|
||||||
|
- linux
|
||||||
|
- unix
|
||||||
|
method: push_account
|
|
@ -0,0 +1,13 @@
|
||||||
|
- hosts: windows
|
||||||
|
gather_facts: yes
|
||||||
|
tasks:
|
||||||
|
- name: Add user account.username
|
||||||
|
ansible.windows.win_user:
|
||||||
|
vars:
|
||||||
|
fullname: "{{ account.username }}"
|
||||||
|
name: "{{ account.username }}"
|
||||||
|
password: "{{ account.secret }}"
|
||||||
|
state: present
|
||||||
|
password_expired: no
|
||||||
|
update_password: always
|
||||||
|
password_never_expires: yes
|
|
@ -0,0 +1,7 @@
|
||||||
|
id: push_account_windows
|
||||||
|
name: Push account windows
|
||||||
|
version: 1
|
||||||
|
method: push_account
|
||||||
|
category: host
|
||||||
|
type:
|
||||||
|
- windows
|
|
@ -0,0 +1,17 @@
|
||||||
|
from common.utils import get_logger
|
||||||
|
from assets.const import AutomationTypes
|
||||||
|
from ..base.manager import BasePlaybookManager, PushOrVerifyHostCallbackMixin
|
||||||
|
|
||||||
|
logger = get_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class PushAccountManager(PushOrVerifyHostCallbackMixin, BasePlaybookManager):
|
||||||
|
ignore_account = True
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self.host_account_mapper = {}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def method_type(cls):
|
||||||
|
return AutomationTypes.push_account
|
|
@ -0,0 +1,13 @@
|
||||||
|
- hosts: mysql
|
||||||
|
gather_facts: no
|
||||||
|
vars:
|
||||||
|
ansible_python_interpreter: /usr/local/bin/python
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
- name: Verify account
|
||||||
|
community.mysql.mysql_info:
|
||||||
|
login_user: "{{ account.username }}"
|
||||||
|
login_password: "{{ account.secret }}"
|
||||||
|
login_host: "{{ jms_asset.address }}"
|
||||||
|
login_port: "{{ jms_asset.port }}"
|
||||||
|
filter: version
|
|
@ -0,0 +1,6 @@
|
||||||
|
id: verify_account_mysql
|
||||||
|
name: Verify account from MySQL
|
||||||
|
category: database
|
||||||
|
type:
|
||||||
|
- mysql
|
||||||
|
method: verify_account
|
|
@ -0,0 +1,13 @@
|
||||||
|
- hosts: postgresql
|
||||||
|
gather_facts: no
|
||||||
|
vars:
|
||||||
|
ansible_python_interpreter: /usr/local/bin/python
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
- name: Verify account
|
||||||
|
community.postgresql.postgresql_ping:
|
||||||
|
login_user: "{{ account.username }}"
|
||||||
|
login_password: "{{ account.secret }}"
|
||||||
|
login_host: "{{ jms_asset.address }}"
|
||||||
|
login_port: "{{ jms_asset.port }}"
|
||||||
|
db: "{{ jms_asset.category_property.db_name }}"
|
|
@ -0,0 +1,6 @@
|
||||||
|
id: verify_account_postgresql
|
||||||
|
name: Verify account for PostgreSQL
|
||||||
|
category: database
|
||||||
|
type:
|
||||||
|
- postgresql
|
||||||
|
method: verify_account
|
|
@ -0,0 +1,11 @@
|
||||||
|
- hosts: demo
|
||||||
|
gather_facts: no
|
||||||
|
tasks:
|
||||||
|
- name: Verify account
|
||||||
|
ansible.builtin.ping:
|
||||||
|
become: no
|
||||||
|
vars:
|
||||||
|
ansible_user: "{{ account.username }}"
|
||||||
|
ansible_password: "{{ account.secret }}"
|
||||||
|
ansible_ssh_private_key_file: "{{ account.private_key_path }}"
|
||||||
|
ansible_become: no
|
|
@ -0,0 +1,7 @@
|
||||||
|
id: verify_account_posix
|
||||||
|
name: Verify posix account
|
||||||
|
category: host
|
||||||
|
type:
|
||||||
|
- linux
|
||||||
|
- unix
|
||||||
|
method: verify_account
|
|
@ -0,0 +1,8 @@
|
||||||
|
- hosts: windows
|
||||||
|
gather_facts: yes
|
||||||
|
tasks:
|
||||||
|
- name: Verify account
|
||||||
|
ansible.windows.win_ping:
|
||||||
|
vars:
|
||||||
|
ansible_user: "{{ account.username }}"
|
||||||
|
ansible_password: "{{ account.secret }}"
|
|
@ -0,0 +1,7 @@
|
||||||
|
id: verify_account_windows
|
||||||
|
name: Verify account windows
|
||||||
|
version: 1
|
||||||
|
method: verify_account
|
||||||
|
category: host
|
||||||
|
type:
|
||||||
|
- windows
|
|
@ -0,0 +1,25 @@
|
||||||
|
from common.utils import get_logger
|
||||||
|
from assets.const import AutomationTypes, Connectivity
|
||||||
|
from ..base.manager import BasePlaybookManager, PushOrVerifyHostCallbackMixin
|
||||||
|
|
||||||
|
logger = get_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class VerifyAccountManager(PushOrVerifyHostCallbackMixin, BasePlaybookManager):
|
||||||
|
ignore_account = False
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self.host_account_mapper = {}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def method_type(cls):
|
||||||
|
return AutomationTypes.verify_account
|
||||||
|
|
||||||
|
def on_host_success(self, host, result):
|
||||||
|
account = self.host_account_mapper.get(host)
|
||||||
|
account.set_connectivity(Connectivity.ok)
|
||||||
|
|
||||||
|
def on_host_error(self, host, error, result):
|
||||||
|
account = self.host_account_mapper.get(host)
|
||||||
|
account.set_connectivity(Connectivity.failed)
|
|
@ -111,7 +111,7 @@ class Migration(migrations.Migration):
|
||||||
('baseautomation_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='assets.baseautomation')),
|
('baseautomation_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='assets.baseautomation')),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'verbose_name': 'Push automation',
|
'verbose_name': 'Push asset account',
|
||||||
},
|
},
|
||||||
bases=('assets.baseautomation',),
|
bases=('assets.baseautomation',),
|
||||||
),
|
),
|
||||||
|
@ -121,7 +121,7 @@ class Migration(migrations.Migration):
|
||||||
('baseautomation_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='assets.baseautomation')),
|
('baseautomation_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='assets.baseautomation')),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'verbose_name': 'Verify account automation',
|
'verbose_name': 'Verify asset account',
|
||||||
},
|
},
|
||||||
bases=('assets.baseautomation',),
|
bases=('assets.baseautomation',),
|
||||||
),
|
),
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
from .change_secret import *
|
from .change_secret import *
|
||||||
from .discovery_account import *
|
from .discovery_account import *
|
||||||
from .push_account import *
|
from .push_account import *
|
||||||
from .verify_secret import *
|
|
||||||
from .gather_facts import *
|
from .gather_facts import *
|
||||||
from .gather_accounts import *
|
from .gather_accounts import *
|
||||||
|
from .verify_account import *
|
||||||
|
|
|
@ -2,7 +2,6 @@ from django.db import models
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from common.db import fields
|
from common.db import fields
|
||||||
from common.const.choices import Trigger
|
|
||||||
from common.db.models import JMSBaseModel
|
from common.db.models import JMSBaseModel
|
||||||
from assets.const import AutomationTypes, SecretType, SecretStrategy, SSHKeyStrategy
|
from assets.const import AutomationTypes, SecretType, SecretStrategy, SSHKeyStrategy
|
||||||
from .base import BaseAutomation
|
from .base import BaseAutomation
|
||||||
|
|
|
@ -1,15 +1,16 @@
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
from assets.const import AutomationTypes
|
||||||
from .base import BaseAutomation
|
from .base import BaseAutomation
|
||||||
|
|
||||||
|
__all__ = ['PushAccountAutomation']
|
||||||
|
|
||||||
|
|
||||||
class PushAccountAutomation(BaseAutomation):
|
class PushAccountAutomation(BaseAutomation):
|
||||||
class Meta:
|
|
||||||
verbose_name = _("Push automation")
|
|
||||||
|
|
||||||
def to_attr_json(self):
|
def save(self, *args, **kwargs):
|
||||||
attr_json = super().to_attr_json()
|
self.type = AutomationTypes.verify_account
|
||||||
attr_json.update({
|
super().save(*args, **kwargs)
|
||||||
'type': 'push_account'
|
|
||||||
})
|
class Meta:
|
||||||
return attr_json
|
verbose_name = _("Push asset account")
|
||||||
|
|
|
@ -1,12 +1,15 @@
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
from assets.const import AutomationTypes
|
||||||
from .base import BaseAutomation
|
from .base import BaseAutomation
|
||||||
|
|
||||||
|
__all__ = ['VerifyAccountAutomation']
|
||||||
|
|
||||||
|
|
||||||
class VerifyAccountAutomation(BaseAutomation):
|
class VerifyAccountAutomation(BaseAutomation):
|
||||||
class Meta:
|
|
||||||
verbose_name = _("Verify account automation")
|
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
self.type = 'verify_account'
|
self.type = AutomationTypes.verify_account
|
||||||
super().save(*args, **kwargs)
|
super().save(*args, **kwargs)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _("Verify asset account")
|
|
@ -49,7 +49,7 @@ class DefaultCallback:
|
||||||
}
|
}
|
||||||
self.result['ok'][host][task] = detail
|
self.result['ok'][host][task] = detail
|
||||||
|
|
||||||
def runer_on_failed(self, event_data, host=None, task=None, res=None, **kwargs):
|
def runner_on_failed(self, event_data, host=None, task=None, res=None, **kwargs):
|
||||||
detail = {
|
detail = {
|
||||||
'action': event_data.get('task_action', ''),
|
'action': event_data.get('task_action', ''),
|
||||||
'res': res,
|
'res': res,
|
||||||
|
|
|
@ -9,16 +9,18 @@ __all__ = ['JMSInventory']
|
||||||
|
|
||||||
|
|
||||||
class JMSInventory:
|
class JMSInventory:
|
||||||
def __init__(self, manager, assets=None, account_policy='smart', account_prefer='root,administrator'):
|
def __init__(self, assets, account_policy='smart',
|
||||||
|
account_prefer='root,administrator',
|
||||||
|
host_callback=None):
|
||||||
"""
|
"""
|
||||||
:param assets:
|
:param assets:
|
||||||
:param account_prefer: account username name if not set use account_policy
|
:param account_prefer: account username name if not set use account_policy
|
||||||
:param account_policy: smart, privileged_must, privileged_first
|
:param account_policy: smart, privileged_must, privileged_first
|
||||||
"""
|
"""
|
||||||
self.manager = manager
|
|
||||||
self.assets = self.clean_assets(assets)
|
self.assets = self.clean_assets(assets)
|
||||||
self.account_prefer = account_prefer
|
self.account_prefer = account_prefer
|
||||||
self.account_policy = account_policy
|
self.account_policy = account_policy
|
||||||
|
self.host_callback = host_callback
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def clean_assets(assets):
|
def clean_assets(assets):
|
||||||
|
@ -61,7 +63,6 @@ class JMSInventory:
|
||||||
var = {
|
var = {
|
||||||
'ansible_user': account.username,
|
'ansible_user': account.username,
|
||||||
}
|
}
|
||||||
|
|
||||||
if not account.secret:
|
if not account.secret:
|
||||||
return var
|
return var
|
||||||
if account.secret_type == 'password':
|
if account.secret_type == 'password':
|
||||||
|
@ -78,10 +79,7 @@ class JMSInventory:
|
||||||
ssh_protocol_matched = list(filter(lambda x: x.name == 'ssh', protocols))
|
ssh_protocol_matched = list(filter(lambda x: x.name == 'ssh', protocols))
|
||||||
ssh_protocol = ssh_protocol_matched[0] if ssh_protocol_matched else None
|
ssh_protocol = ssh_protocol_matched[0] if ssh_protocol_matched else None
|
||||||
host['ansible_host'] = asset.address
|
host['ansible_host'] = asset.address
|
||||||
if asset.port == 0:
|
host['ansible_port'] = ssh_protocol.port if ssh_protocol else 22
|
||||||
host['ansible_port'] = ssh_protocol.port if ssh_protocol else 22
|
|
||||||
else:
|
|
||||||
host['ansible_port'] = asset.port
|
|
||||||
|
|
||||||
su_from = account.su_from
|
su_from = account.su_from
|
||||||
if platform.su_enabled and su_from:
|
if platform.su_enabled and su_from:
|
||||||
|
@ -106,7 +104,8 @@ class JMSInventory:
|
||||||
'jms_asset': {
|
'jms_asset': {
|
||||||
'id': str(asset.id), 'name': asset.name, 'address': asset.address,
|
'id': str(asset.id), 'name': asset.name, 'address': asset.address,
|
||||||
'type': asset.type, 'category': asset.category,
|
'type': asset.type, 'category': asset.category,
|
||||||
'protocol': asset.protocol, 'port': asset.port,'database': '',
|
'protocol': asset.protocol, 'port': asset.port,
|
||||||
|
'category_property': asset.category_property,
|
||||||
'protocols': [{'name': p.name, 'port': p.port} for p in protocols],
|
'protocols': [{'name': p.name, 'port': p.port} for p in protocols],
|
||||||
},
|
},
|
||||||
'jms_account': {
|
'jms_account': {
|
||||||
|
@ -118,9 +117,6 @@ class JMSInventory:
|
||||||
ansible_connection = ansible_config.get('ansible_connection', 'ssh')
|
ansible_connection = ansible_config.get('ansible_connection', 'ssh')
|
||||||
host.update(ansible_config)
|
host.update(ansible_config)
|
||||||
|
|
||||||
if platform.category == 'database':
|
|
||||||
host['jms_asset']['database'] = asset.database.db_name
|
|
||||||
|
|
||||||
gateway = None
|
gateway = None
|
||||||
if asset.domain:
|
if asset.domain:
|
||||||
gateway = asset.domain.select_gateway()
|
gateway = asset.domain.select_gateway()
|
||||||
|
@ -167,17 +163,17 @@ class JMSInventory:
|
||||||
platform_assets = self.group_by_platform(self.assets)
|
platform_assets = self.group_by_platform(self.assets)
|
||||||
for platform, assets in platform_assets.items():
|
for platform, assets in platform_assets.items():
|
||||||
automation = platform.automation
|
automation = platform.automation
|
||||||
protocols = platform.protocols.all()
|
|
||||||
|
|
||||||
for asset in assets:
|
for asset in assets:
|
||||||
|
protocols = asset.protocols.all()
|
||||||
account = self.select_account(asset)
|
account = self.select_account(asset)
|
||||||
host = self.asset_to_host(asset, account, automation, protocols, platform)
|
host = self.asset_to_host(asset, account, automation, protocols, platform)
|
||||||
|
|
||||||
if not automation.ansible_enabled:
|
if not automation.ansible_enabled:
|
||||||
host['error'] = _('Ansible disabled')
|
host['error'] = _('Ansible disabled')
|
||||||
|
|
||||||
if self.manager.host_callback is not None:
|
if self.host_callback is not None:
|
||||||
host = self.manager.host_callback(
|
host = self.host_callback(
|
||||||
host, asset=asset, account=account,
|
host, asset=asset, account=account,
|
||||||
platform=platform, automation=automation,
|
platform=platform, automation=automation,
|
||||||
path_dir=path_dir
|
path_dir=path_dir
|
||||||
|
|
|
@ -99,7 +99,7 @@ class CeleryPeriodTaskViewSet(CommonApiMixin, viewsets.ModelViewSet):
|
||||||
|
|
||||||
|
|
||||||
class CeleryTaskViewSet(CommonApiMixin, viewsets.ReadOnlyModelViewSet):
|
class CeleryTaskViewSet(CommonApiMixin, viewsets.ReadOnlyModelViewSet):
|
||||||
queryset = CeleryTask.objects.filter(name__in=['ops.tasks.hello', 'ops.tasks.hello_error', 'ops.tasks.hello_random'])
|
queryset = CeleryTask.objects.all()
|
||||||
serializer_class = CeleryTaskSerializer
|
serializer_class = CeleryTaskSerializer
|
||||||
http_method_names = ('get', 'head', 'options',)
|
http_method_names = ('get', 'head', 'options',)
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import os.path
|
|
||||||
import shutil
|
import shutil
|
||||||
import zipfile
|
import zipfile
|
||||||
import yaml
|
import yaml
|
||||||
|
import os.path
|
||||||
|
|
||||||
from django.core.files.storage import default_storage
|
from django.core.files.storage import default_storage
|
||||||
from rest_framework import viewsets
|
from rest_framework import viewsets
|
||||||
|
@ -9,12 +9,16 @@ from rest_framework.decorators import action
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework.serializers import ValidationError
|
from rest_framework.serializers import ValidationError
|
||||||
|
|
||||||
from terminal import serializers, models
|
from terminal import serializers
|
||||||
|
from terminal.models import AppletPublication, Applet
|
||||||
from terminal.serializers import AppletUploadSerializer
|
from terminal.serializers import AppletUploadSerializer
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = ['AppletViewSet', 'AppletPublicationViewSet']
|
||||||
|
|
||||||
|
|
||||||
class AppletViewSet(viewsets.ModelViewSet):
|
class AppletViewSet(viewsets.ModelViewSet):
|
||||||
queryset = models.Applet.objects.all()
|
queryset = Applet.objects.all()
|
||||||
serializer_class = serializers.AppletSerializer
|
serializer_class = serializers.AppletSerializer
|
||||||
rbac_perms = {
|
rbac_perms = {
|
||||||
'upload': 'terminal.add_applet',
|
'upload': 'terminal.add_applet',
|
||||||
|
@ -67,7 +71,7 @@ class AppletViewSet(viewsets.ModelViewSet):
|
||||||
name = manifest['name']
|
name = manifest['name']
|
||||||
update = request.query_params.get('update')
|
update = request.query_params.get('update')
|
||||||
|
|
||||||
instance = models.Applet.objects.filter(name=name).first()
|
instance = Applet.objects.filter(name=name).first()
|
||||||
if instance and not update:
|
if instance and not update:
|
||||||
return Response({'error': 'Applet already exists: {}'.format(name)}, status=400)
|
return Response({'error': 'Applet already exists: {}'.format(name)}, status=400)
|
||||||
|
|
||||||
|
@ -82,5 +86,5 @@ class AppletViewSet(viewsets.ModelViewSet):
|
||||||
|
|
||||||
|
|
||||||
class AppletPublicationViewSet(viewsets.ModelViewSet):
|
class AppletPublicationViewSet(viewsets.ModelViewSet):
|
||||||
queryset = models.AppletPublication.objects.all()
|
queryset = AppletPublication.objects.all()
|
||||||
serializer_class = serializers.AppletPublicationSerializer
|
serializer_class = serializers.AppletPublicationSerializer
|
||||||
|
|
|
@ -1,23 +1,35 @@
|
||||||
from rest_framework import viewsets
|
from rest_framework import viewsets
|
||||||
|
from rest_framework.decorators import action
|
||||||
|
from rest_framework.response import Response
|
||||||
|
|
||||||
from orgs.utils import tmp_to_builtin_org
|
from orgs.utils import tmp_to_builtin_org
|
||||||
from terminal import serializers, models
|
from terminal import serializers
|
||||||
|
from terminal.models import AppletHost, Applet
|
||||||
|
from terminal.tasks import run_applet_host_deployment
|
||||||
|
|
||||||
__all__ = ['AppletHostViewSet', 'AppletHostDeploymentViewSet']
|
__all__ = ['AppletHostViewSet']
|
||||||
|
|
||||||
|
|
||||||
class AppletHostViewSet(viewsets.ModelViewSet):
|
class AppletHostViewSet(viewsets.ModelViewSet):
|
||||||
serializer_class = serializers.AppletHostSerializer
|
serializer_class = serializers.AppletHostSerializer
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
return models.AppletHost.objects.all()
|
return AppletHost.objects.all()
|
||||||
|
|
||||||
def dispatch(self, request, *args, **kwargs):
|
def dispatch(self, request, *args, **kwargs):
|
||||||
with tmp_to_builtin_org(system=1):
|
with tmp_to_builtin_org(system=1):
|
||||||
return super().dispatch(request, *args, **kwargs)
|
return super().dispatch(request, *args, **kwargs)
|
||||||
|
|
||||||
|
@action(methods=['post'], detail=True)
|
||||||
|
def deploy(self, request):
|
||||||
|
from terminal.automations.deploy_applet_host.manager import DeployAppletHostManager
|
||||||
|
manager = DeployAppletHostManager(self)
|
||||||
|
manager.run()
|
||||||
|
|
||||||
class AppletHostDeploymentViewSet(viewsets.ModelViewSet):
|
@action(methods=['get'], detail=True, url_path='')
|
||||||
queryset = models.AppletHostDeployment.objects.all()
|
def not_published_applets(self, request, *args, **kwargs):
|
||||||
serializer_class = serializers.AppletHostDeploymentSerializer
|
instance = self.get_object()
|
||||||
|
applets = Applet.objects.exclude(id__in=instance.applets.all())
|
||||||
|
serializer = serializers.AppletSerializer(applets, many=True)
|
||||||
|
return Response(serializer.data)
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,43 @@
|
||||||
|
import os
|
||||||
|
import datetime
|
||||||
|
import shutil
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
from ops.ansible import PlaybookRunner, JMSInventory
|
||||||
|
|
||||||
|
CURRENT_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
|
||||||
|
|
||||||
|
class DeployAppletHostManager:
|
||||||
|
def __init__(self, applet_host):
|
||||||
|
self.applet_host = applet_host
|
||||||
|
self.run_dir = self.get_run_dir()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_run_dir():
|
||||||
|
base = os.path.join(settings.ANSIBLE_DIR, 'applet_host_deploy')
|
||||||
|
now = datetime.datetime.now().strftime('%Y%m%d%H%M%S')
|
||||||
|
return os.path.join(base, now)
|
||||||
|
|
||||||
|
def generate_playbook(self):
|
||||||
|
playbook_src = os.path.join(CURRENT_DIR, 'playbook.yml')
|
||||||
|
playbook_dir = os.path.join(self.run_dir, 'playbook')
|
||||||
|
playbook_dst = os.path.join(playbook_dir, 'main.yml')
|
||||||
|
os.makedirs(playbook_dir, exist_ok=True)
|
||||||
|
shutil.copy(playbook_src, playbook_dst)
|
||||||
|
return playbook_dst
|
||||||
|
|
||||||
|
def generate_inventory(self):
|
||||||
|
inventory = JMSInventory([self.applet_host], account_policy='privileged_only')
|
||||||
|
inventory_dir = os.path.join(self.run_dir, 'inventory')
|
||||||
|
inventory_path = os.path.join(inventory_dir, 'hosts.yml')
|
||||||
|
inventory.write_to_file(inventory_path)
|
||||||
|
return inventory_path
|
||||||
|
|
||||||
|
def run(self, **kwargs):
|
||||||
|
inventory = self.generate_inventory()
|
||||||
|
playbook = self.generate_playbook()
|
||||||
|
runner = PlaybookRunner(
|
||||||
|
inventory=inventory, playbook=playbook, project_dir=self.run_dir
|
||||||
|
)
|
||||||
|
return runner.run(**kwargs)
|
|
@ -1,56 +1,138 @@
|
||||||
---
|
---
|
||||||
- hosts: windows
|
|
||||||
|
- hosts: all
|
||||||
vars:
|
vars:
|
||||||
- DownloadHost: https://demo.jumpserver.org/download
|
- DownloadHost: https://demo.jumpserver.org/download
|
||||||
- RDS_Licensing: enabled
|
- RDS_Licensing: enabled
|
||||||
- RDS_LicenseServer: 127.0.0.1
|
- RDS_LicenseServer: 127.0.0.1
|
||||||
- RDS_LicensingMode: 4
|
- RDS_LicensingMode: 4
|
||||||
- RDS_fSingleSessionPerUser: 0
|
- RDS_fSingleSessionPerUser: 1
|
||||||
- RDS_MaxDisconnectionTime: 60000
|
- RDS_MaxDisconnectionTime: 60000
|
||||||
- RDS_RemoteAppLogoffTimeLimit: 0
|
- RDS_RemoteAppLogoffTimeLimit: 0
|
||||||
|
|
||||||
tasks:
|
tasks:
|
||||||
- name: Install RDS-Licensing (RDS)
|
- name: Install RDS-Licensing (RDS)
|
||||||
ansible.windows.win_feature:
|
ansible.windows.win_feature:
|
||||||
name: RDS-Licensing
|
name: RDS-Licensing
|
||||||
state: present
|
state: present
|
||||||
include_management_tools: yes
|
include_management_tools: yes
|
||||||
when: RDS_Licensing == "enabled"
|
when: RDS_Licensing == "enabled"
|
||||||
- name: Install RDS-RD-Server (RDS)
|
|
||||||
ansible.windows.win_feature:
|
- name: Install RDS-RD-Server (RDS)
|
||||||
name: RDS-RD-Server
|
ansible.windows.win_feature:
|
||||||
state: present
|
name: RDS-RD-Server
|
||||||
include_management_tools: yes
|
state: present
|
||||||
register: win_feature
|
include_management_tools: yes
|
||||||
- name: Reboot if installing RDS feature requires it
|
register: rds_install
|
||||||
ansible.windows.win_reboot:
|
|
||||||
when: win_feature.reboot_required
|
- name: Download Jmservisor (jumpserver)
|
||||||
- name: Set RDS LicenseServer (regedit)
|
ansible.windows.win_get_url:
|
||||||
ansible.windows.win_regedit:
|
url: "{{ DownloadHost }}/Jmservisor.msi"
|
||||||
path: HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services
|
dest: "{{ ansible_env.TEMP }}\\Jmservisor.msi"
|
||||||
name: LicenseServers
|
|
||||||
data: "{{ RDS_LicenseServer }}"
|
- name: Install the Jmservisor (jumpserver)
|
||||||
type: string
|
ansible.windows.win_package:
|
||||||
- name: Set RDS LicensingMode (regedit)
|
path: "{{ ansible_env.TEMP }}\\Jmservisor.msi"
|
||||||
ansible.windows.win_regedit:
|
state: present
|
||||||
path: HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services
|
|
||||||
name: LicensingMode
|
- name: Download python-3.10.8
|
||||||
data: "{{ RDS_LicensingMode }}"
|
ansible.windows.win_get_url:
|
||||||
type: dword
|
url: "{{ DownloadHost }}/python-3.10.8-amd64.exe"
|
||||||
- name: Set RDS fSingleSessionPerUser (regedit)
|
dest: "{{ ansible_env.TEMP }}\\python-3.10.8-amd64.exe"
|
||||||
ansible.windows.win_regedit:
|
|
||||||
path: HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services
|
- name: Install the python-3.10.8
|
||||||
name: fSingleSessionPerUser
|
ansible.windows.win_package:
|
||||||
data: "{{ RDS_fSingleSessionPerUser }}"
|
path: "{{ ansible_env.TEMP }}\\python-3.10.8-amd64.exe"
|
||||||
type: dword
|
product_id: '{371d0d73-d418-4ffe-b280-58c3e7987525}'
|
||||||
- name: Set RDS MaxDisconnectionTime (regedit)
|
arguments:
|
||||||
ansible.windows.win_regedit:
|
- /quiet
|
||||||
path: HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services
|
- InstallAllUsers=1
|
||||||
name: MaxDisconnectionTime
|
- PrependPath=1
|
||||||
data: "{{ RDS_MaxDisconnectionTime }}"
|
- Include_test=0
|
||||||
type: dword
|
- Include_launcher=0
|
||||||
when: RDS_MaxDisconnectionTime >= 60000
|
state: present
|
||||||
- name: Set RDS RemoteAppLogoffTimeLimit (regedit)
|
register: win_install_python
|
||||||
ansible.windows.win_regedit:
|
|
||||||
path: HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services
|
- name: Reboot if installing requires it
|
||||||
name: RemoteAppLogoffTimeLimit
|
ansible.windows.win_reboot:
|
||||||
data: "{{ RDS_RemoteAppLogoffTime }}"
|
post_reboot_delay: 10
|
||||||
|
test_command: whoami
|
||||||
|
when: rds_install.reboot_required or win_install_python.reboot_required
|
||||||
|
|
||||||
|
- name: Set RDS LicenseServer (regedit)
|
||||||
|
ansible.windows.win_regedit:
|
||||||
|
path: HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services
|
||||||
|
name: LicenseServers
|
||||||
|
data: "{{ RDS_LicenseServer }}"
|
||||||
|
type: string
|
||||||
|
|
||||||
|
- name: Set RDS LicensingMode (regedit)
|
||||||
|
ansible.windows.win_regedit:
|
||||||
|
path: HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services
|
||||||
|
name: LicensingMode
|
||||||
|
data: "{{ RDS_LicensingMode }}"
|
||||||
|
type: dword
|
||||||
|
|
||||||
|
- name: Set RDS fSingleSessionPerUser (regedit)
|
||||||
|
ansible.windows.win_regedit:
|
||||||
|
path: HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services
|
||||||
|
name: fSingleSessionPerUser
|
||||||
|
data: "{{ RDS_fSingleSessionPerUser }}"
|
||||||
|
type: dword
|
||||||
|
|
||||||
|
- name: Set RDS MaxDisconnectionTime (regedit)
|
||||||
|
ansible.windows.win_regedit:
|
||||||
|
path: HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services
|
||||||
|
name: MaxDisconnectionTime
|
||||||
|
data: "{{ RDS_MaxDisconnectionTime }}"
|
||||||
|
type: dword
|
||||||
|
when: RDS_MaxDisconnectionTime >= 60000
|
||||||
|
|
||||||
|
- name: Set RDS RemoteAppLogoffTimeLimit (regedit)
|
||||||
|
ansible.windows.win_regedit:
|
||||||
|
path: HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services
|
||||||
|
name: RemoteAppLogoffTimeLimit
|
||||||
|
data: "{{ RDS_RemoteAppLogoffTimeLimit }}"
|
||||||
|
type: dword
|
||||||
|
|
||||||
|
- name: Download pip packages
|
||||||
|
ansible.windows.win_get_url:
|
||||||
|
url: "{{ DownloadHost }}/pip_packages_v0.0.1.zip"
|
||||||
|
dest: "{{ ansible_env.TEMP }}\\pip_packages_v0.0.1.zip"
|
||||||
|
|
||||||
|
- name: Unzip pip_packages
|
||||||
|
community.windows.win_unzip:
|
||||||
|
src: "{{ ansible_env.TEMP }}\\pip_packages_v0.0.1.zip"
|
||||||
|
dest: "{{ ansible_env.TEMP }}"
|
||||||
|
|
||||||
|
- name: Install python requirements offline
|
||||||
|
ansible.windows.win_shell: >
|
||||||
|
pip install -r '{{ ansible_env.TEMP }}\pip_packages_v0.0.1\requirements.txt'
|
||||||
|
--no-index --find-links='{{ ansible_env.TEMP }}\pip_packages_v0.0.1'
|
||||||
|
|
||||||
|
- name: Download chromedriver (chrome)
|
||||||
|
ansible.windows.win_get_url:
|
||||||
|
url: "{{ DownloadHost }}/chromedriver_win32.106.zip"
|
||||||
|
dest: "{{ ansible_env.TEMP }}\\chromedriver_win32.106.zip"
|
||||||
|
|
||||||
|
- name: Unzip chromedriver (chrome)
|
||||||
|
community.windows.win_unzip:
|
||||||
|
src: "{{ ansible_env.TEMP }}\\chromedriver_win32.106.zip"
|
||||||
|
dest: C:\Program Files\JumpServer\drivers
|
||||||
|
|
||||||
|
- name: Set chromedriver on the global system path (chrome)
|
||||||
|
ansible.windows.win_path:
|
||||||
|
elements:
|
||||||
|
- 'C:\Program Files\JumpServer\drivers'
|
||||||
|
|
||||||
|
- name: Download chrome msi package (chrome)
|
||||||
|
ansible.windows.win_get_url:
|
||||||
|
url: "{{ DownloadHost }}/googlechromestandaloneenterprise64.msi"
|
||||||
|
dest: "{{ ansible_env.TEMP }}\\googlechromestandaloneenterprise64.msi"
|
||||||
|
|
||||||
|
- name: Install chrome (chrome)
|
||||||
|
ansible.windows.win_package:
|
||||||
|
path: "{{ ansible_env.TEMP }}\\googlechromestandaloneenterprise64.msi"
|
||||||
|
state: present
|
||||||
|
arguments:
|
||||||
|
- /quiet
|
||||||
|
|
|
@ -0,0 +1,42 @@
|
||||||
|
# Generated by Django 3.2.14 on 2022-10-28 07:44
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('terminal', '0054_auto_20221027_1125'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='applet',
|
||||||
|
name='hosts',
|
||||||
|
field=models.ManyToManyField(through='terminal.AppletPublication', to='terminal.AppletHost', verbose_name='Hosts'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='applethost',
|
||||||
|
name='date_inited',
|
||||||
|
field=models.DateTimeField(blank=True, null=True, verbose_name='Date initialized'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='applethost',
|
||||||
|
name='initialized',
|
||||||
|
field=models.BooleanField(default=False, verbose_name='Initialized'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='appletpublication',
|
||||||
|
name='applet',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='publications', to='terminal.applet', verbose_name='Applet'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='appletpublication',
|
||||||
|
name='host',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='publications', to='terminal.applethost', verbose_name='Host'),
|
||||||
|
),
|
||||||
|
migrations.DeleteModel(
|
||||||
|
name='AppletHostDeployment',
|
||||||
|
),
|
||||||
|
]
|
|
@ -26,6 +26,7 @@ class Applet(JMSBaseModel):
|
||||||
protocols = models.JSONField(default=list, verbose_name=_('Protocol'))
|
protocols = models.JSONField(default=list, verbose_name=_('Protocol'))
|
||||||
tags = models.JSONField(default=list, verbose_name=_('Tags'))
|
tags = models.JSONField(default=list, verbose_name=_('Tags'))
|
||||||
comment = models.TextField(default='', blank=True, verbose_name=_('Comment'))
|
comment = models.TextField(default='', blank=True, verbose_name=_('Comment'))
|
||||||
|
hosts = models.ManyToManyField(through_fields=('applet', 'host'), through='AppletPublication', to='AppletHost', verbose_name=_('Hosts'))
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
@ -51,8 +52,8 @@ class Applet(JMSBaseModel):
|
||||||
|
|
||||||
|
|
||||||
class AppletPublication(JMSBaseModel):
|
class AppletPublication(JMSBaseModel):
|
||||||
applet = models.ForeignKey('Applet', on_delete=models.PROTECT, verbose_name=_('Applet'))
|
applet = models.ForeignKey('Applet', on_delete=models.PROTECT, related_name='publications', verbose_name=_('Applet'))
|
||||||
host = models.ForeignKey('AppletHost', on_delete=models.PROTECT, verbose_name=_('Host'))
|
host = models.ForeignKey('AppletHost', on_delete=models.PROTECT, related_name='publications', verbose_name=_('Host'))
|
||||||
status = models.CharField(max_length=16, verbose_name=_('Status'))
|
status = models.CharField(max_length=16, verbose_name=_('Status'))
|
||||||
comment = models.TextField(default='', blank=True, verbose_name=_('Comment'))
|
comment = models.TextField(default='', blank=True, verbose_name=_('Comment'))
|
||||||
|
|
||||||
|
|
|
@ -1,15 +1,17 @@
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from common.db.models import JMSBaseModel
|
|
||||||
from assets.models import Host
|
from assets.models import Host
|
||||||
|
from ops.ansible import PlaybookRunner, JMSInventory
|
||||||
|
|
||||||
|
|
||||||
__all__ = ['AppletHost', 'AppletHostDeployment']
|
__all__ = ['AppletHost']
|
||||||
|
|
||||||
|
|
||||||
class AppletHost(Host):
|
class AppletHost(Host):
|
||||||
account_automation = models.BooleanField(default=False, verbose_name=_('Account automation'))
|
account_automation = models.BooleanField(default=False, verbose_name=_('Account automation'))
|
||||||
|
initialized = models.BooleanField(default=False, verbose_name=_('Initialized'))
|
||||||
|
date_inited = models.DateTimeField(null=True, blank=True, verbose_name=_('Date initialized'))
|
||||||
date_synced = models.DateTimeField(null=True, blank=True, verbose_name=_('Date synced'))
|
date_synced = models.DateTimeField(null=True, blank=True, verbose_name=_('Date synced'))
|
||||||
status = models.CharField(max_length=16, verbose_name=_('Status'))
|
status = models.CharField(max_length=16, verbose_name=_('Status'))
|
||||||
applets = models.ManyToManyField(
|
applets = models.ManyToManyField(
|
||||||
|
@ -17,17 +19,11 @@ class AppletHost(Host):
|
||||||
through='AppletPublication', through_fields=('host', 'applet'),
|
through='AppletPublication', through_fields=('host', 'applet'),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def deploy(self):
|
||||||
|
inventory = JMSInventory([self])
|
||||||
|
playbook = PlaybookRunner(inventory, 'applets.yml')
|
||||||
|
playbook.run()
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
|
|
||||||
class AppletHostDeployment(JMSBaseModel):
|
|
||||||
host = models.ForeignKey('AppletHost', on_delete=models.CASCADE, verbose_name=_('Hosting'))
|
|
||||||
status = models.CharField(max_length=16, verbose_name=_('Status'))
|
|
||||||
comment = models.TextField(default='', blank=True, verbose_name=_('Comment'))
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.host
|
|
||||||
|
|
||||||
def start(self):
|
|
||||||
pass
|
|
||||||
|
|
|
@ -5,12 +5,12 @@ from common.drf.fields import ObjectRelatedField, LabeledChoiceField
|
||||||
from common.validators import ProjectUniqueValidator
|
from common.validators import ProjectUniqueValidator
|
||||||
from assets.models import Platform
|
from assets.models import Platform
|
||||||
from assets.serializers import HostSerializer
|
from assets.serializers import HostSerializer
|
||||||
from ..models import Applet, AppletPublication, AppletHost, AppletHostDeployment
|
from ..models import Applet, AppletPublication, AppletHost
|
||||||
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'AppletSerializer', 'AppletPublicationSerializer',
|
'AppletSerializer', 'AppletPublicationSerializer',
|
||||||
'AppletHostSerializer', 'AppletHostDeploymentSerializer',
|
'AppletHostSerializer',
|
||||||
'AppletUploadSerializer'
|
'AppletUploadSerializer'
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -85,14 +85,3 @@ class AppletHostSerializer(HostSerializer):
|
||||||
validators.append(uniq_validator)
|
validators.append(uniq_validator)
|
||||||
return validators
|
return validators
|
||||||
|
|
||||||
|
|
||||||
class AppletHostDeploymentSerializer(serializers.ModelSerializer):
|
|
||||||
host = ObjectRelatedField(queryset=AppletHost.objects.all())
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = AppletHostDeployment
|
|
||||||
fields_mini = ['id', 'host']
|
|
||||||
read_only_fields = ['date_created', 'date_updated']
|
|
||||||
fields = fields_mini + [
|
|
||||||
'status', 'comment',
|
|
||||||
] + read_only_fields
|
|
||||||
|
|
|
@ -11,7 +11,7 @@ from common.utils import get_disk_usage, get_cpu_load, get_memory_usage, get_log
|
||||||
|
|
||||||
from .serializers.terminal import TerminalRegistrationSerializer, StatusSerializer
|
from .serializers.terminal import TerminalRegistrationSerializer, StatusSerializer
|
||||||
from .const import TerminalTypeChoices
|
from .const import TerminalTypeChoices
|
||||||
from .models.terminal import Terminal
|
from .models import Terminal
|
||||||
|
|
||||||
__all__ = ['CoreTerminal', 'CeleryTerminal']
|
__all__ = ['CoreTerminal', 'CeleryTerminal']
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,7 @@ from common.utils import get_log_keep_day
|
||||||
from ops.celery.decorator import (
|
from ops.celery.decorator import (
|
||||||
register_as_period_task, after_app_ready_start, after_app_shutdown_clean_periodic
|
register_as_period_task, after_app_ready_start, after_app_shutdown_clean_periodic
|
||||||
)
|
)
|
||||||
from .models import Status, Session, Command, Task
|
from .models import Status, Session, Command, Task, AppletHost
|
||||||
from .backends import server_replay_storage
|
from .backends import server_replay_storage
|
||||||
from .utils import find_session_replay_local
|
from .utils import find_session_replay_local
|
||||||
|
|
||||||
|
@ -99,3 +99,9 @@ def upload_session_replay_to_external_storage(session_id):
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
|
@shared_task
|
||||||
|
def run_applet_host_deployment(did):
|
||||||
|
host = AppletHost.objects.get(id=did)
|
||||||
|
host.deploy()
|
||||||
|
|
|
@ -26,8 +26,7 @@ router.register(r'endpoints', api.EndpointViewSet, 'endpoint')
|
||||||
router.register(r'endpoint-rules', api.EndpointRuleViewSet, 'endpoint-rule')
|
router.register(r'endpoint-rules', api.EndpointRuleViewSet, 'endpoint-rule')
|
||||||
router.register(r'applets', api.AppletViewSet, 'applet')
|
router.register(r'applets', api.AppletViewSet, 'applet')
|
||||||
router.register(r'applet-hosts', api.AppletHostViewSet, 'applet-host')
|
router.register(r'applet-hosts', api.AppletHostViewSet, 'applet-host')
|
||||||
router.register(r'applet-publication', api.AppletPublicationViewSet, 'applet-publication')
|
router.register(r'applet-publications', api.AppletPublicationViewSet, 'applet-publication')
|
||||||
router.register(r'applet-host-deployment', api.AppletHostDeploymentViewSet, 'applet-host-deployment')
|
|
||||||
|
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
|
@ -46,10 +45,6 @@ urlpatterns = [
|
||||||
path('command-storages/<uuid:pk>/test-connective/', api.CommandStorageTestConnectiveApi.as_view(), name='command-storage-test-connective'),
|
path('command-storages/<uuid:pk>/test-connective/', api.CommandStorageTestConnectiveApi.as_view(), name='command-storage-test-connective'),
|
||||||
# components
|
# components
|
||||||
path('components/metrics/', api.ComponentsMetricsAPIView.as_view(), name='components-metrics'),
|
path('components/metrics/', api.ComponentsMetricsAPIView.as_view(), name='components-metrics'),
|
||||||
# v2: get session's replay
|
|
||||||
# path('v2/sessions/<uuid:pk>/replay/',
|
|
||||||
# api.SessionReplayV2ViewSet.as_view({'get': 'retrieve'}),
|
|
||||||
# name='session-replay-v2'),
|
|
||||||
]
|
]
|
||||||
|
|
||||||
old_version_urlpatterns = [
|
old_version_urlpatterns = [
|
||||||
|
|
Loading…
Reference in New Issue