mirror of
https://github.com/jumpserver/jumpserver.git
synced 2025-12-15 08:32:48 +00:00
Compare commits
8 Commits
pr@dev@fea
...
pr@dev@ter
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
caa675442a | ||
|
|
be7a4c0d6e | ||
|
|
009da19050 | ||
|
|
dfda6b1e08 | ||
|
|
59b40578d8 | ||
|
|
e5db28c014 | ||
|
|
6d1f26b0f8 | ||
|
|
2333dbbe33 |
@@ -113,6 +113,18 @@ class BaseChangeSecretPushManager(AccountBasePlaybookManager):
|
||||
if host.get('error'):
|
||||
return host
|
||||
|
||||
inventory_hosts = []
|
||||
if asset.type == HostTypes.WINDOWS:
|
||||
if self.secret_type == SecretType.SSH_KEY:
|
||||
host['error'] = _("Windows does not support SSH key authentication")
|
||||
return host
|
||||
|
||||
if self.secret_strategy == SecretStrategy.custom:
|
||||
new_secret = self.execution.snapshot['secret']
|
||||
if '>' in new_secret or '^' in new_secret:
|
||||
host['error'] = _("Windows password cannot contain special characters like > ^")
|
||||
return host
|
||||
|
||||
host['ssh_params'] = {}
|
||||
|
||||
accounts = self.get_accounts(account)
|
||||
@@ -130,11 +142,6 @@ class BaseChangeSecretPushManager(AccountBasePlaybookManager):
|
||||
if asset.type == HostTypes.WINDOWS:
|
||||
accounts = accounts.filter(secret_type=SecretType.PASSWORD)
|
||||
|
||||
inventory_hosts = []
|
||||
if asset.type == HostTypes.WINDOWS and self.secret_type == SecretType.SSH_KEY:
|
||||
print(f'Windows {asset} does not support ssh key push')
|
||||
return inventory_hosts
|
||||
|
||||
for account in accounts:
|
||||
h = deepcopy(host)
|
||||
h['name'] += '(' + account.username + ')' # To distinguish different accounts
|
||||
|
||||
@@ -5,12 +5,14 @@
|
||||
|
||||
tasks:
|
||||
- name: Test SQLServer connection
|
||||
community.general.mssql_script:
|
||||
mssql_script:
|
||||
login_user: "{{ jms_account.username }}"
|
||||
login_password: "{{ jms_account.secret }}"
|
||||
login_host: "{{ jms_asset.address }}"
|
||||
login_port: "{{ jms_asset.port }}"
|
||||
name: '{{ jms_asset.spec_info.db_name }}'
|
||||
encryption: "{{ jms_asset.encryption | default(None) }}"
|
||||
tds_version: "{{ jms_asset.tds_version | default(None) }}"
|
||||
script: |
|
||||
SELECT @@version
|
||||
register: db_info
|
||||
@@ -23,45 +25,53 @@
|
||||
var: info
|
||||
|
||||
- name: Check whether SQLServer User exist
|
||||
community.general.mssql_script:
|
||||
mssql_script:
|
||||
login_user: "{{ jms_account.username }}"
|
||||
login_password: "{{ jms_account.secret }}"
|
||||
login_host: "{{ jms_asset.address }}"
|
||||
login_port: "{{ jms_asset.port }}"
|
||||
name: '{{ jms_asset.spec_info.db_name }}'
|
||||
encryption: "{{ jms_asset.encryption | default(None) }}"
|
||||
tds_version: "{{ jms_asset.tds_version | default(None) }}"
|
||||
script: "SELECT 1 from sys.sql_logins WHERE name='{{ account.username }}';"
|
||||
when: db_info is succeeded
|
||||
register: user_exist
|
||||
|
||||
- name: Change SQLServer password
|
||||
community.general.mssql_script:
|
||||
mssql_script:
|
||||
login_user: "{{ jms_account.username }}"
|
||||
login_password: "{{ jms_account.secret }}"
|
||||
login_host: "{{ jms_asset.address }}"
|
||||
login_port: "{{ jms_asset.port }}"
|
||||
name: '{{ jms_asset.spec_info.db_name }}'
|
||||
encryption: "{{ jms_asset.encryption | default(None) }}"
|
||||
tds_version: "{{ jms_asset.tds_version | default(None) }}"
|
||||
script: "ALTER LOGIN {{ account.username }} WITH PASSWORD = '{{ account.secret }}', DEFAULT_DATABASE = {{ jms_asset.spec_info.db_name }}; select @@version"
|
||||
ignore_errors: true
|
||||
when: user_exist.query_results[0] | length != 0
|
||||
|
||||
- name: Add SQLServer user
|
||||
community.general.mssql_script:
|
||||
mssql_script:
|
||||
login_user: "{{ jms_account.username }}"
|
||||
login_password: "{{ jms_account.secret }}"
|
||||
login_host: "{{ jms_asset.address }}"
|
||||
login_port: "{{ jms_asset.port }}"
|
||||
name: '{{ jms_asset.spec_info.db_name }}'
|
||||
encryption: "{{ jms_asset.encryption | default(None) }}"
|
||||
tds_version: "{{ jms_asset.tds_version | default(None) }}"
|
||||
script: "CREATE LOGIN {{ account.username }} WITH PASSWORD = '{{ account.secret }}', DEFAULT_DATABASE = {{ jms_asset.spec_info.db_name }}; CREATE USER {{ account.username }} FOR LOGIN {{ account.username }}; select @@version"
|
||||
ignore_errors: true
|
||||
when: user_exist.query_results[0] | length == 0
|
||||
|
||||
- name: Verify password
|
||||
community.general.mssql_script:
|
||||
mssql_script:
|
||||
login_user: "{{ account.username }}"
|
||||
login_password: "{{ account.secret }}"
|
||||
login_host: "{{ jms_asset.address }}"
|
||||
login_port: "{{ jms_asset.port }}"
|
||||
name: '{{ jms_asset.spec_info.db_name }}'
|
||||
encryption: "{{ jms_asset.encryption | default(None) }}"
|
||||
tds_version: "{{ jms_asset.tds_version | default(None) }}"
|
||||
script: |
|
||||
SELECT @@version
|
||||
when: check_conn_after_change
|
||||
|
||||
@@ -9,7 +9,7 @@ from accounts.const import (
|
||||
AutomationTypes, SecretStrategy, ChangeSecretRecordStatusChoice
|
||||
)
|
||||
from accounts.models import ChangeSecretRecord
|
||||
from accounts.notifications import ChangeSecretExecutionTaskMsg, ChangeSecretReportMsg
|
||||
from accounts.notifications import ChangeSecretExecutionTaskMsg
|
||||
from accounts.serializers import ChangeSecretRecordBackUpSerializer
|
||||
from common.utils import get_logger
|
||||
from common.utils.file import encrypt_and_compress_zip_file
|
||||
@@ -94,10 +94,6 @@ class ChangeSecretManager(BaseChangeSecretPushManager):
|
||||
if not recipients:
|
||||
return
|
||||
|
||||
context = self.get_report_context()
|
||||
for user in recipients:
|
||||
ChangeSecretReportMsg(user, context).publish()
|
||||
|
||||
if not records:
|
||||
return
|
||||
|
||||
|
||||
@@ -5,12 +5,14 @@
|
||||
|
||||
tasks:
|
||||
- name: Test SQLServer connection
|
||||
community.general.mssql_script:
|
||||
mssql_script:
|
||||
login_user: "{{ jms_account.username }}"
|
||||
login_password: "{{ jms_account.secret }}"
|
||||
login_host: "{{ jms_asset.address }}"
|
||||
login_port: "{{ jms_asset.port }}"
|
||||
name: '{{ jms_asset.spec_info.db_name }}'
|
||||
encryption: "{{ jms_asset.encryption | default(None) }}"
|
||||
tds_version: "{{ jms_asset.tds_version | default(None) }}"
|
||||
script: |
|
||||
SELECT
|
||||
l.name,
|
||||
|
||||
@@ -5,12 +5,14 @@
|
||||
|
||||
tasks:
|
||||
- name: Test SQLServer connection
|
||||
community.general.mssql_script:
|
||||
mssql_script:
|
||||
login_user: "{{ jms_account.username }}"
|
||||
login_password: "{{ jms_account.secret }}"
|
||||
login_host: "{{ jms_asset.address }}"
|
||||
login_port: "{{ jms_asset.port }}"
|
||||
name: '{{ jms_asset.spec_info.db_name }}'
|
||||
encryption: "{{ jms_asset.encryption | default(None) }}"
|
||||
tds_version: "{{ jms_asset.tds_version | default(None) }}"
|
||||
script: |
|
||||
SELECT @@version
|
||||
register: db_info
|
||||
@@ -23,47 +25,55 @@
|
||||
var: info
|
||||
|
||||
- name: Check whether SQLServer User exist
|
||||
community.general.mssql_script:
|
||||
mssql_script:
|
||||
login_user: "{{ jms_account.username }}"
|
||||
login_password: "{{ jms_account.secret }}"
|
||||
login_host: "{{ jms_asset.address }}"
|
||||
login_port: "{{ jms_asset.port }}"
|
||||
name: '{{ jms_asset.spec_info.db_name }}'
|
||||
encryption: "{{ jms_asset.encryption | default(None) }}"
|
||||
tds_version: "{{ jms_asset.tds_version | default(None) }}"
|
||||
script: "SELECT 1 from sys.sql_logins WHERE name='{{ account.username }}';"
|
||||
when: db_info is succeeded
|
||||
register: user_exist
|
||||
|
||||
- name: Change SQLServer password
|
||||
community.general.mssql_script:
|
||||
mssql_script:
|
||||
login_user: "{{ jms_account.username }}"
|
||||
login_password: "{{ jms_account.secret }}"
|
||||
login_host: "{{ jms_asset.address }}"
|
||||
login_port: "{{ jms_asset.port }}"
|
||||
name: '{{ jms_asset.spec_info.db_name }}'
|
||||
encryption: "{{ jms_asset.encryption | default(None) }}"
|
||||
tds_version: "{{ jms_asset.tds_version | default(None) }}"
|
||||
script: "ALTER LOGIN {{ account.username }} WITH PASSWORD = '{{ account.secret }}', DEFAULT_DATABASE = {{ jms_asset.spec_info.db_name }}; select @@version"
|
||||
ignore_errors: true
|
||||
when: user_exist.query_results[0] | length != 0
|
||||
register: change_info
|
||||
|
||||
- name: Add SQLServer user
|
||||
community.general.mssql_script:
|
||||
mssql_script:
|
||||
login_user: "{{ jms_account.username }}"
|
||||
login_password: "{{ jms_account.secret }}"
|
||||
login_host: "{{ jms_asset.address }}"
|
||||
login_port: "{{ jms_asset.port }}"
|
||||
name: '{{ jms_asset.spec_info.db_name }}'
|
||||
encryption: "{{ jms_asset.encryption | default(None) }}"
|
||||
tds_version: "{{ jms_asset.tds_version | default(None) }}"
|
||||
script: "CREATE LOGIN [{{ account.username }}] WITH PASSWORD = '{{ account.secret }}'; CREATE USER [{{ account.username }}] FOR LOGIN [{{ account.username }}]; select @@version"
|
||||
ignore_errors: true
|
||||
when: user_exist.query_results[0] | length == 0
|
||||
register: change_info
|
||||
|
||||
- name: Verify password
|
||||
community.general.mssql_script:
|
||||
mssql_script:
|
||||
login_user: "{{ account.username }}"
|
||||
login_password: "{{ account.secret }}"
|
||||
login_host: "{{ jms_asset.address }}"
|
||||
login_port: "{{ jms_asset.port }}"
|
||||
name: '{{ jms_asset.spec_info.db_name }}'
|
||||
encryption: "{{ jms_asset.encryption | default(None) }}"
|
||||
tds_version: "{{ jms_asset.tds_version | default(None) }}"
|
||||
script: |
|
||||
SELECT @@version
|
||||
when: check_conn_after_change
|
||||
|
||||
@@ -5,11 +5,13 @@
|
||||
|
||||
tasks:
|
||||
- name: "Remove account"
|
||||
community.general.mssql_script:
|
||||
mssql_script:
|
||||
login_user: "{{ jms_account.username }}"
|
||||
login_password: "{{ jms_account.secret }}"
|
||||
login_host: "{{ jms_asset.address }}"
|
||||
login_port: "{{ jms_asset.port }}"
|
||||
name: "{{ jms_asset.spec_info.db_name }}"
|
||||
encryption: "{{ jms_asset.encryption | default(None) }}"
|
||||
tds_version: "{{ jms_asset.tds_version | default(None) }}"
|
||||
script: "DROP LOGIN {{ account.username }}; select @@version"
|
||||
|
||||
|
||||
@@ -5,11 +5,13 @@
|
||||
|
||||
tasks:
|
||||
- name: Verify account
|
||||
community.general.mssql_script:
|
||||
mssql_script:
|
||||
login_user: "{{ account.username }}"
|
||||
login_password: "{{ account.secret }}"
|
||||
login_host: "{{ jms_asset.address }}"
|
||||
login_port: "{{ jms_asset.port }}"
|
||||
name: '{{ jms_asset.spec_info.db_name }}'
|
||||
encryption: "{{ jms_asset.encryption | default(None) }}"
|
||||
tds_version: "{{ jms_asset.tds_version | default(None) }}"
|
||||
script: |
|
||||
SELECT @@version
|
||||
|
||||
@@ -253,6 +253,8 @@ class AccountSerializer(AccountCreateUpdateSerializerMixin, BaseAccountSerialize
|
||||
'source_id': {'required': False, 'allow_null': True},
|
||||
}
|
||||
fields_unimport_template = ['params']
|
||||
# 手动判断唯一性校验
|
||||
validators = []
|
||||
|
||||
@classmethod
|
||||
def setup_eager_loading(cls, queryset):
|
||||
@@ -263,6 +265,21 @@ class AccountSerializer(AccountCreateUpdateSerializerMixin, BaseAccountSerialize
|
||||
)
|
||||
return queryset
|
||||
|
||||
def validate(self, attrs):
|
||||
instance = getattr(self, "instance", None)
|
||||
if instance:
|
||||
return super().validate(attrs)
|
||||
|
||||
field_errors = {}
|
||||
for _fields in Account._meta.unique_together:
|
||||
lookup = {field: attrs.get(field) for field in _fields}
|
||||
if Account.objects.filter(**lookup).exists():
|
||||
verbose_names = ', '.join([str(Account._meta.get_field(f).verbose_name) for f in _fields])
|
||||
msg_template = _('Account already exists. Field(s): {fields} must be unique.')
|
||||
field_errors[_fields[0]] = msg_template.format(fields=verbose_names)
|
||||
raise serializers.ValidationError(field_errors)
|
||||
return attrs
|
||||
|
||||
|
||||
class AccountDetailSerializer(AccountSerializer):
|
||||
has_secret = serializers.BooleanField(label=_("Has secret"), read_only=True)
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
{% load i18n %}
|
||||
|
||||
<h3>{% trans 'Task name' %}: {{ name }}</h3>
|
||||
<h3>{% trans 'Task execution id' %}: {{ execution_id }}</h3>
|
||||
<p>{% trans 'Respectful' %} {{ recipient }}</p>
|
||||
<p>{% trans 'Hello! The following is the failure of changing the password of your assets or pushing the account. Please check and handle it in time.' %}</p>
|
||||
<table style="width: 100%; border-collapse: collapse; max-width: 100%; text-align: left; margin-top: 20px;">
|
||||
<caption></caption>
|
||||
<thead>
|
||||
<tr style="background-color: #f2f2f2;">
|
||||
<th style="border: 1px solid #ddd; padding: 10px;">{% trans 'Asset' %}</th>
|
||||
<th style="border: 1px solid #ddd; padding: 10px;">{% trans 'Account' %}</th>
|
||||
<th style="border: 1px solid #ddd; padding: 10px;">{% trans 'Error' %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for asset_name, account_username, error in asset_account_errors %}
|
||||
<tr>
|
||||
<td style="border: 1px solid #ddd; padding: 10px;">{{ asset_name }}</td>
|
||||
<td style="border: 1px solid #ddd; padding: 10px;">{{ account_username }}</td>
|
||||
<td style="border: 1px solid #ddd; padding: 10px;">
|
||||
<div style="
|
||||
max-width: 90%;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
display: block;"
|
||||
title="{{ error }}"
|
||||
>
|
||||
{{ error }}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
@@ -6,11 +6,13 @@
|
||||
|
||||
tasks:
|
||||
- name: Test SQLServer connection
|
||||
community.general.mssql_script:
|
||||
mssql_script:
|
||||
login_user: "{{ jms_account.username }}"
|
||||
login_password: "{{ jms_account.secret }}"
|
||||
login_host: "{{ jms_asset.address }}"
|
||||
login_port: "{{ jms_asset.port }}"
|
||||
name: '{{ jms_asset.spec_info.db_name }}'
|
||||
encryption: "{{ jms_asset.encryption | default(None) }}"
|
||||
tds_version: "{{ jms_asset.tds_version | default(None) }}"
|
||||
script: |
|
||||
SELECT @@version
|
||||
|
||||
@@ -250,6 +250,12 @@ class Protocol(ChoicesMixin, models.TextChoices):
|
||||
'default': False,
|
||||
'label': _('Auth username')
|
||||
},
|
||||
'enable_cluster_mode': {
|
||||
'type': 'bool',
|
||||
'default': False,
|
||||
'label': _('Enable cluster mode'),
|
||||
'help_text': _('Enable if this Redis instance is part of a cluster')
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ class MaxLimitOffsetPagination(LimitOffsetPagination):
|
||||
self.max_limit = view.page_max_limit
|
||||
|
||||
# 自定义的 api view,就默认不约束分页了
|
||||
if getattr(view, 'action') != 'list' and not getattr(view, 'default_limit'):
|
||||
if getattr(view, 'action', None) != 'list' and not getattr(view, 'default_limit', None):
|
||||
self.default_limit = None
|
||||
|
||||
if view and hasattr(view, 'page_default_limit'):
|
||||
|
||||
401
apps/libs/ansible/modules/mssql_script.py
Normal file
401
apps/libs/ansible/modules/mssql_script.py
Normal file
@@ -0,0 +1,401 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
# Modified from ansible_collections.community.general.mssql_script
|
||||
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = r"""
|
||||
module: mssql_script
|
||||
|
||||
short_description: Execute SQL scripts on a MSSQL database
|
||||
|
||||
version_added: "1.0.0"
|
||||
|
||||
description:
|
||||
- Execute SQL scripts on a MSSQL database.
|
||||
extends_documentation_fragment:
|
||||
- community.general.attributes
|
||||
|
||||
attributes:
|
||||
check_mode:
|
||||
support: partial
|
||||
details:
|
||||
- The script is not be executed in check mode.
|
||||
diff_mode:
|
||||
support: none
|
||||
|
||||
options:
|
||||
name:
|
||||
description: Database to run script against.
|
||||
aliases: [db]
|
||||
default: ''
|
||||
type: str
|
||||
login_user:
|
||||
description: The username used to authenticate with.
|
||||
type: str
|
||||
login_password:
|
||||
description: The password used to authenticate with.
|
||||
type: str
|
||||
login_host:
|
||||
description: Host running the database.
|
||||
type: str
|
||||
required: true
|
||||
login_port:
|
||||
description: Port of the MSSQL server. Requires O(login_host) be defined as well.
|
||||
default: 1433
|
||||
type: int
|
||||
script:
|
||||
description:
|
||||
- The SQL script to be executed.
|
||||
- Script can contain multiple SQL statements. Multiple Batches can be separated by V(GO) command.
|
||||
- Each batch must return at least one result set.
|
||||
required: true
|
||||
type: str
|
||||
transaction:
|
||||
description:
|
||||
- If transactional mode is requested, start a transaction and commit the change only if the script succeed. Otherwise,
|
||||
rollback the transaction.
|
||||
- If transactional mode is not requested (default), automatically commit the change.
|
||||
type: bool
|
||||
default: false
|
||||
version_added: 8.4.0
|
||||
output:
|
||||
description:
|
||||
- With V(default) each row is returned as a list of values. See RV(query_results).
|
||||
- Output format V(dict) returns dictionary with the column names as keys. See RV(query_results_dict).
|
||||
- V(dict) requires named columns to be returned by each query otherwise an error is thrown.
|
||||
choices: ["dict", "default"]
|
||||
default: 'default'
|
||||
type: str
|
||||
params:
|
||||
description: |-
|
||||
Parameters passed to the script as SQL parameters.
|
||||
(Query V('SELECT %(name\)s"') with V(example: '{"name": "John Doe"}).)'.
|
||||
type: dict
|
||||
notes:
|
||||
- Requires the pymssql Python package on the remote host. For Ubuntu, this is as easy as C(pip install pymssql) (See M(ansible.builtin.pip)).
|
||||
requirements:
|
||||
- pymssql
|
||||
|
||||
author:
|
||||
- Kris Budde (@kbudde)
|
||||
"""
|
||||
|
||||
EXAMPLES = r"""
|
||||
- name: Check DB connection
|
||||
community.general.mssql_script:
|
||||
login_user: "{{ mssql_login_user }}"
|
||||
login_password: "{{ mssql_login_password }}"
|
||||
login_host: "{{ mssql_host }}"
|
||||
login_port: "{{ mssql_port }}"
|
||||
db: master
|
||||
script: "SELECT 1"
|
||||
|
||||
- name: Query with parameter
|
||||
community.general.mssql_script:
|
||||
login_user: "{{ mssql_login_user }}"
|
||||
login_password: "{{ mssql_login_password }}"
|
||||
login_host: "{{ mssql_host }}"
|
||||
login_port: "{{ mssql_port }}"
|
||||
script: |
|
||||
SELECT name, state_desc FROM sys.databases WHERE name = %(dbname)s
|
||||
params:
|
||||
dbname: msdb
|
||||
register: result_params
|
||||
- assert:
|
||||
that:
|
||||
- result_params.query_results[0][0][0][0] == 'msdb'
|
||||
- result_params.query_results[0][0][0][1] == 'ONLINE'
|
||||
|
||||
- name: Query within a transaction
|
||||
community.general.mssql_script:
|
||||
login_user: "{{ mssql_login_user }}"
|
||||
login_password: "{{ mssql_login_password }}"
|
||||
login_host: "{{ mssql_host }}"
|
||||
login_port: "{{ mssql_port }}"
|
||||
script: |
|
||||
UPDATE sys.SomeTable SET desc = 'some_table_desc' WHERE name = %(dbname)s
|
||||
UPDATE sys.AnotherTable SET desc = 'another_table_desc' WHERE name = %(dbname)s
|
||||
transaction: true
|
||||
params:
|
||||
dbname: msdb
|
||||
|
||||
- name: two batches with default output
|
||||
community.general.mssql_script:
|
||||
login_user: "{{ mssql_login_user }}"
|
||||
login_password: "{{ mssql_login_password }}"
|
||||
login_host: "{{ mssql_host }}"
|
||||
login_port: "{{ mssql_port }}"
|
||||
script: |
|
||||
SELECT 'Batch 0 - Select 0'
|
||||
SELECT 'Batch 0 - Select 1'
|
||||
GO
|
||||
SELECT 'Batch 1 - Select 0'
|
||||
register: result_batches
|
||||
- assert:
|
||||
that:
|
||||
- result_batches.query_results | length == 2 # two batch results
|
||||
- result_batches.query_results[0] | length == 2 # two selects in first batch
|
||||
- result_batches.query_results[0][0] | length == 1 # one row in first select
|
||||
- result_batches.query_results[0][0][0] | length == 1 # one column in first row
|
||||
- result_batches.query_results[0][0][0][0] == 'Batch 0 - Select 0' # each row contains a list of values.
|
||||
|
||||
- name: two batches with dict output
|
||||
community.general.mssql_script:
|
||||
login_user: "{{ mssql_login_user }}"
|
||||
login_password: "{{ mssql_login_password }}"
|
||||
login_host: "{{ mssql_host }}"
|
||||
login_port: "{{ mssql_port }}"
|
||||
output: dict
|
||||
script: |
|
||||
SELECT 'Batch 0 - Select 0' as b0s0
|
||||
SELECT 'Batch 0 - Select 1' as b0s1
|
||||
GO
|
||||
SELECT 'Batch 1 - Select 0' as b1s0
|
||||
register: result_batches_dict
|
||||
- assert:
|
||||
that:
|
||||
- result_batches_dict.query_results_dict | length == 2 # two batch results
|
||||
- result_batches_dict.query_results_dict[0] | length == 2 # two selects in first batch
|
||||
- result_batches_dict.query_results_dict[0][0] | length == 1 # one row in first select
|
||||
- result_batches_dict.query_results_dict[0][0][0]['b0s0'] == 'Batch 0 - Select 0' # column 'b0s0' of first row
|
||||
"""
|
||||
|
||||
RETURN = r"""
|
||||
query_results:
|
||||
description: List of batches (queries separated by V(GO) keyword).
|
||||
type: list
|
||||
elements: list
|
||||
returned: success and O(output=default)
|
||||
sample:
|
||||
[
|
||||
[
|
||||
[
|
||||
[
|
||||
"Batch 0 - Select 0"
|
||||
]
|
||||
],
|
||||
[
|
||||
[
|
||||
"Batch 0 - Select 1"
|
||||
]
|
||||
]
|
||||
],
|
||||
[
|
||||
[
|
||||
[
|
||||
"Batch 1 - Select 0"
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
contains:
|
||||
queries:
|
||||
description:
|
||||
- List of result sets of each query.
|
||||
- If a query returns no results, the results of this and all the following queries are not included in the output.
|
||||
- Use the V(GO) keyword in O(script) to separate queries.
|
||||
type: list
|
||||
elements: list
|
||||
contains:
|
||||
rows:
|
||||
description: List of rows returned by query.
|
||||
type: list
|
||||
elements: list
|
||||
contains:
|
||||
column_value:
|
||||
description:
|
||||
- List of column values.
|
||||
- Any non-standard JSON type is converted to string.
|
||||
type: list
|
||||
example: ["Batch 0 - Select 0"]
|
||||
returned: success, if output is default
|
||||
query_results_dict:
|
||||
description: List of batches (queries separated by V(GO) keyword).
|
||||
type: list
|
||||
elements: list
|
||||
returned: success and O(output=dict)
|
||||
sample:
|
||||
[
|
||||
[
|
||||
[
|
||||
[
|
||||
"Batch 0 - Select 0"
|
||||
]
|
||||
],
|
||||
[
|
||||
[
|
||||
"Batch 0 - Select 1"
|
||||
]
|
||||
]
|
||||
],
|
||||
[
|
||||
[
|
||||
[
|
||||
"Batch 1 - Select 0"
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
contains:
|
||||
queries:
|
||||
description:
|
||||
- List of result sets of each query.
|
||||
- If a query returns no results, the results of this and all the following queries are not included in the output.
|
||||
Use V(GO) keyword to separate queries.
|
||||
type: list
|
||||
elements: list
|
||||
contains:
|
||||
rows:
|
||||
description: List of rows returned by query.
|
||||
type: list
|
||||
elements: list
|
||||
contains:
|
||||
column_dict:
|
||||
description:
|
||||
- Dictionary of column names and values.
|
||||
- Any non-standard JSON type is converted to string.
|
||||
type: dict
|
||||
example: {"col_name": "Batch 0 - Select 0"}
|
||||
returned: success, if output is dict
|
||||
"""
|
||||
|
||||
import pymssql
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
import json
|
||||
|
||||
|
||||
def clean_output(o):
|
||||
return str(o)
|
||||
|
||||
|
||||
def main():
|
||||
module_args = dict(
|
||||
name=dict(aliases=['db'], default=''),
|
||||
login_user=dict(type='str', required=False, default=None),
|
||||
login_password=dict(no_log=True),
|
||||
login_host=dict(required=True),
|
||||
login_port=dict(type='int', default=1433),
|
||||
script=dict(required=True),
|
||||
output=dict(default='default', choices=['dict', 'default']),
|
||||
params=dict(type='dict'),
|
||||
transaction=dict(type='bool', default=True),
|
||||
tds_version=dict(type='str', required=False, default=None),
|
||||
encryption=dict(type='str', required=False, default=None)
|
||||
)
|
||||
|
||||
result = dict(
|
||||
changed=False,
|
||||
)
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec=module_args,
|
||||
supports_check_mode=True
|
||||
)
|
||||
|
||||
db = module.params['name']
|
||||
login_user = module.params['login_user']
|
||||
login_password = module.params['login_password']
|
||||
login_host = module.params['login_host']
|
||||
login_port = module.params['login_port']
|
||||
script = module.params['script']
|
||||
output = module.params['output']
|
||||
sql_params = module.params['params']
|
||||
transaction = module.params['transaction']
|
||||
# TODO 待 ansible 官方支持这两个参数
|
||||
tds_version = module.params['tds_version'] or None
|
||||
encryption = module.params['encryption'] or None
|
||||
|
||||
login_querystring = login_host
|
||||
if login_port != 1433:
|
||||
login_querystring = "%s:%s" % (login_host, login_port)
|
||||
|
||||
if login_user is not None and login_password is None:
|
||||
module.fail_json(
|
||||
msg="when supplying login_user argument, login_password must also be provided")
|
||||
|
||||
try:
|
||||
conn = pymssql.connect(
|
||||
user=login_user, password=login_password, host=login_querystring,
|
||||
database=db, encryption=encryption, tds_version=tds_version)
|
||||
cursor = conn.cursor()
|
||||
except Exception as e:
|
||||
if "Unknown database" in str(e):
|
||||
errno, errstr = e.args
|
||||
module.fail_json(msg="ERROR: %s %s" % (errno, errstr))
|
||||
else:
|
||||
module.fail_json(
|
||||
msg="unable to connect, check login_user and login_password are correct, or alternatively check your "
|
||||
"@sysconfdir@/freetds.conf / ${HOME}/.freetds.conf")
|
||||
|
||||
# If transactional mode is requested, start a transaction
|
||||
conn.autocommit(not transaction)
|
||||
|
||||
query_results_key = 'query_results'
|
||||
if output == 'dict':
|
||||
cursor = conn.cursor(as_dict=True)
|
||||
query_results_key = 'query_results_dict'
|
||||
|
||||
# Process the script into batches
|
||||
queries = []
|
||||
current_batch = []
|
||||
for statement in script.splitlines(True):
|
||||
# Ignore the Byte Order Mark, if found
|
||||
if statement.strip() == '\uFEFF':
|
||||
continue
|
||||
|
||||
# Assume each 'GO' is on its own line but may have leading/trailing whitespace
|
||||
# and be of mixed-case
|
||||
if statement.strip().upper() != 'GO':
|
||||
current_batch.append(statement)
|
||||
else:
|
||||
queries.append(''.join(current_batch))
|
||||
current_batch = []
|
||||
if len(current_batch) > 0:
|
||||
queries.append(''.join(current_batch))
|
||||
|
||||
result['changed'] = True
|
||||
if module.check_mode:
|
||||
module.exit_json(**result)
|
||||
|
||||
query_results = []
|
||||
for query in queries:
|
||||
# Catch and exit on any bad query errors
|
||||
try:
|
||||
cursor.execute(query, sql_params)
|
||||
qry_result = []
|
||||
rows = cursor.fetchall()
|
||||
while rows:
|
||||
qry_result.append(rows)
|
||||
rows = cursor.fetchall()
|
||||
query_results.append(qry_result)
|
||||
except Exception as e:
|
||||
# We know we executed the statement so this error just means we have no resultset
|
||||
# which is ok (eg UPDATE/INSERT)
|
||||
if (
|
||||
type(e).__name__ == 'OperationalError' and
|
||||
str(e) == 'Statement not executed or executed statement has no resultset'
|
||||
):
|
||||
query_results.append([])
|
||||
else:
|
||||
# Rollback transaction before failing the module in case of error
|
||||
if transaction:
|
||||
conn.rollback()
|
||||
error_msg = '%s: %s' % (type(e).__name__, str(e))
|
||||
module.fail_json(msg="query failed", query=query, error=error_msg, **result)
|
||||
|
||||
# Commit transaction before exiting the module in case of no error
|
||||
if transaction:
|
||||
conn.commit()
|
||||
|
||||
# ensure that the result is json serializable
|
||||
qry_results = json.loads(json.dumps(query_results, default=clean_output))
|
||||
|
||||
result[query_results_key] = qry_results
|
||||
module.exit_json(**result)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -112,12 +112,21 @@ class JMSInventory:
|
||||
|
||||
@staticmethod
|
||||
def make_protocol_setting_vars(host, protocols):
|
||||
# 针对 ssh 协议的特殊处理
|
||||
# 针对 ssh sqlserver 协议的特殊处理
|
||||
for p in protocols:
|
||||
if p.name == 'ssh':
|
||||
if hasattr(p, 'setting'):
|
||||
setting = getattr(p, 'setting')
|
||||
host['old_ssh_version'] = setting.get('old_ssh_version', False)
|
||||
host['jms_asset']['old_ssh_version'] = setting.get('old_ssh_version', False)
|
||||
if p.name == 'sqlserver':
|
||||
if hasattr(p, 'setting'):
|
||||
setting = getattr(p, 'setting')
|
||||
encryption = setting.get('encrypt', True)
|
||||
version = setting.get('version', ">=2014")
|
||||
if version == '<2014':
|
||||
host['jms_asset']['tds_version'] = '7.0'
|
||||
if not encryption:
|
||||
host['jms_asset']['encryption'] = 'off'
|
||||
|
||||
def make_account_vars(self, host, asset, account, automation, protocol, platform, gateway, path_dir,
|
||||
ansible_config):
|
||||
@@ -226,7 +235,6 @@ class JMSInventory:
|
||||
})
|
||||
|
||||
self.make_protocol_setting_vars(host, protocols)
|
||||
|
||||
protocols = host['jms_asset']['protocols']
|
||||
host['jms_asset'].update({f"{p['name']}_port": p['port'] for p in protocols})
|
||||
if host['jms_account'] and tp == 'oracle':
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
import uuid
|
||||
from collections import defaultdict
|
||||
from datetime import timedelta, datetime
|
||||
|
||||
import sys
|
||||
from celery import current_task
|
||||
from django.conf import settings
|
||||
from django.db import models
|
||||
@@ -271,7 +271,7 @@ class JobExecution(JMSOrgBaseModel):
|
||||
db_module_name_map = {
|
||||
'mysql': 'community.mysql.mysql_query',
|
||||
'postgresql': 'community.postgresql.postgresql_query',
|
||||
'sqlserver': 'community.general.mssql_script',
|
||||
'sqlserver': 'mssql_script',
|
||||
}
|
||||
extra_query_token_map = {
|
||||
'sqlserver': 'script'
|
||||
@@ -292,7 +292,10 @@ class JobExecution(JMSOrgBaseModel):
|
||||
"login_user={{login_user}} " \
|
||||
"login_password={{login_password}} " \
|
||||
"login_port={{login_port}} " \
|
||||
"%s={{login_db}}" % login_db_token
|
||||
"%s={{login_db}} " % login_db_token
|
||||
if module == 'mssql_script':
|
||||
login_args += "encryption={{jms_asset.encryption | default(None) }} " \
|
||||
"tds_version={{jms_asset.tds_version | default(None) }} "
|
||||
shell = "{} {}=\"{}\" ".format(login_args, query_token, self.current_job.args)
|
||||
return module, shell
|
||||
|
||||
@@ -450,6 +453,8 @@ class JobExecution(JMSOrgBaseModel):
|
||||
"input": self.material,
|
||||
"risk_level": RiskLevelChoices.reject,
|
||||
"user": self.creator,
|
||||
'org_id': self.org_id,
|
||||
'_org_name': self.org_name,
|
||||
}).publish_async()
|
||||
raise Exception("command is rejected by ACL")
|
||||
elif acl.is_action(CommandFilterACL.ActionChoices.warning):
|
||||
@@ -488,6 +493,8 @@ class JobExecution(JMSOrgBaseModel):
|
||||
"input": self.material,
|
||||
"risk_level": RiskLevelChoices.reject,
|
||||
"user": self.creator,
|
||||
'org_id': self.org_id,
|
||||
'_org_name': self.org_name,
|
||||
}).publish_async()
|
||||
raise CommandInBlackListException(
|
||||
"Command is rejected by black list: {}".format(self.current_job.args))
|
||||
|
||||
@@ -152,6 +152,8 @@ class CommandAlertMessage(CommandAlertMixin, SystemMessage):
|
||||
def gen_html_string(self, **other_context) -> dict:
|
||||
command = self.command
|
||||
level = RiskLevelChoices.get_label(command['risk_level'])
|
||||
org_id = command['org_id']
|
||||
org_name = command.get('_org_name') or org_id
|
||||
items = {
|
||||
_("Asset"): command['asset'],
|
||||
_("User"): command['user'],
|
||||
@@ -161,6 +163,7 @@ class CommandAlertMessage(CommandAlertMixin, SystemMessage):
|
||||
context = {
|
||||
'items': items,
|
||||
"command": command['input'],
|
||||
'org': org_name,
|
||||
}
|
||||
context.update(other_context)
|
||||
message = render_to_string('terminal/_msg_command_alert.html', context)
|
||||
@@ -212,7 +215,8 @@ class CommandExecutionAlert(CommandAlertMixin, SystemMessage):
|
||||
def gen_html_string(self, **other_context):
|
||||
command = self.command
|
||||
level = RiskLevelChoices.get_label(command['risk_level'])
|
||||
|
||||
org_id = command['org_id']
|
||||
org_name = command.get('_org_name') or org_id
|
||||
items = {
|
||||
_("User"): command['user'],
|
||||
_("Level"): level,
|
||||
@@ -221,6 +225,7 @@ class CommandExecutionAlert(CommandAlertMixin, SystemMessage):
|
||||
context = {
|
||||
'items': items,
|
||||
'command': command['input'],
|
||||
'org': org_name,
|
||||
}
|
||||
context.update(other_context)
|
||||
message = render_to_string('terminal/_msg_command_execute_alert.html', context)
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
{% endfor %}
|
||||
<b>{% trans 'Session' %}:</b> <a href="{{ session_url }}" target="_blank">{% trans 'view' %}</a>
|
||||
<br />
|
||||
<b>{% trans 'Organization' %}: </b> <span>{{ org }}</span>
|
||||
<br/>
|
||||
<b>{% trans 'Command' %}: </b><br />
|
||||
<pre>
|
||||
{{ command }}
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
</span>
|
||||
<br />
|
||||
{% endfor %}
|
||||
<b>{% trans 'Organization' %}: </b> <span>{{ org }}</span>
|
||||
<br/>
|
||||
<b>{% trans 'Command' %}: </b><br />
|
||||
<pre>
|
||||
{{ command }}
|
||||
|
||||
@@ -10,7 +10,7 @@ from rest_framework_bulk.generics import BulkModelViewSet
|
||||
|
||||
from common.api import CommonApiMixin, SuggestionMixin
|
||||
from common.drf.filters import AttrRulesFilterBackend
|
||||
from common.utils import get_logger
|
||||
from common.utils import get_logger, is_uuid
|
||||
from orgs.utils import current_org, tmp_to_root_org
|
||||
from rbac.models import Role, RoleBinding
|
||||
from rbac.permissions import RBACPermission
|
||||
@@ -52,6 +52,12 @@ class UserViewSet(CommonApiMixin, UserQuerysetMixin, SuggestionMixin, BulkModelV
|
||||
'bulk_remove': 'users.remove_user',
|
||||
}
|
||||
|
||||
def get_object(self):
|
||||
pk = self.kwargs.get(self.lookup_field)
|
||||
if not is_uuid(pk):
|
||||
return self.get_queryset().get(username=pk)
|
||||
return super().get_object()
|
||||
|
||||
def allow_bulk_destroy(self, qs, filtered):
|
||||
is_valid = filtered.count() < qs.count()
|
||||
if not is_valid:
|
||||
|
||||
@@ -146,6 +146,11 @@ class AuthMixin:
|
||||
def can_create_ssh_key(self):
|
||||
return self.ssh_keys.count() < settings.TERMINAL_SSH_KEY_LIMIT_COUNT
|
||||
|
||||
@lazyproperty
|
||||
def has_public_keys(self):
|
||||
count = self.ssh_keys.filter(is_active=True).count()
|
||||
return count > 0
|
||||
|
||||
def can_update_password(self):
|
||||
return self.is_local
|
||||
|
||||
|
||||
@@ -394,9 +394,13 @@ class UserRetrieveSerializer(UserSerializer):
|
||||
login_confirm_settings = serializers.PrimaryKeyRelatedField(
|
||||
read_only=True, source="login_confirm_setting.reviewers", many=True
|
||||
)
|
||||
has_public_keys = serializers.BooleanField(
|
||||
label=_("Has public keys"),
|
||||
read_only=True,
|
||||
)
|
||||
|
||||
class Meta(UserSerializer.Meta):
|
||||
fields = UserSerializer.Meta.fields + ["login_confirm_settings"]
|
||||
fields = UserSerializer.Meta.fields + ["login_confirm_settings", "has_public_keys"]
|
||||
|
||||
|
||||
class SmsUserSerializer(serializers.ModelSerializer):
|
||||
|
||||
Reference in New Issue
Block a user