feat: 支持部分资源的自定义自动化任务(Ping/VerifyAccount/ChangeSecret) (#9947)

* feat: 支持部分资源的自定义自动化任务(Ping/VerifyAccount/ChangeSecret)

* perf: 去掉无用的属性

* perf: 优化自定义改密逻辑

* feat: 支持ssh_key认证

* perf: 去掉无用注释

* perf: 优化

* perf: 优化逻辑

* perf: 优化标题

* perf: 去掉一些无用的函数

* perf: 优化helptext
pull/10213/head
jiangweidong 2023-04-14 18:31:09 +08:00 committed by GitHub
parent f07e4e53ec
commit 690e01cb78
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 367 additions and 10 deletions

View File

@ -0,0 +1,40 @@
- hosts: custom
gather_facts: no
vars:
ansible_connection: local
tasks:
- name: Test privileged account
ssh_ping:
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
login_user: "{{ jms_account.username }}"
login_password: "{{ jms_account.secret }}"
login_secret_type: "{{ jms_account.secret_type }}"
login_private_key_path: "{{ jms_account.private_key_path }}"
register: ping_info
- name: Change asset password
custom_command:
login_user: "{{ jms_account.username }}"
login_password: "{{ jms_account.secret }}"
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
login_secret_type: "{{ jms_account.secret_type }}"
login_private_key_path: "{{ jms_account.private_key_path }}"
name: "{{ account.username }}"
password: "{{ account.secret }}"
commands: "{{ params.commands }}"
first_conn_delay_time: "{{ first_conn_delay_time | default(0.5) }}"
when: ping_info is succeeded
register: change_info
- name: Verify password
ssh_ping:
login_user: "{{ account.username }}"
login_password: "{{ account.secret }}"
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
when:
- ping_info is succeeded
- change_info is succeeded

View File

@ -0,0 +1,14 @@
id: change_secret_by_ssh
name: Change secret by SSH
category:
- device
- host
type:
- all
method: change_secret
params:
- name: commands
type: list
label: '自定义命令'
default: ['']
help_text: '自定义命令中如需包含账号的 username 和 password 字段,请使用 &#123;username&#125;、&#123;password&#125;格式,执行任务时会进行替换 。<br />比如针对 Linux 主机进行改密,一般需要配置三条命令:<br />1.passwd &#123;username&#125; <br />2.&#123;password&#125; <br />3.&#123;password&#125;'

View File

@ -0,0 +1,14 @@
- hosts: custom
gather_facts: no
vars:
ansible_connection: local
tasks:
- name: Verify account
ssh_ping:
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
login_user: "{{ account.username }}"
login_password: "{{ account.secret }}"
login_secret_type: "{{ jms_account.secret_type }}"
login_private_key_path: "{{ jms_account.private_key_path }}"

View File

@ -0,0 +1,8 @@
id: verify_account_by_ssh
name: Verify account by SSH
category:
- device
- host
type:
- all
method: verify_account

View File

@ -58,6 +58,7 @@ class ChangeSecretAutomationSerializer(AuthValidateMixin, BaseAutomationSerializ
"Currently only mail sending is supported"
)},
}}
@property
def model_type(self):
return AutomationTypes.change_secret

View File

@ -54,7 +54,9 @@ class BasePlaybookManager:
if serializer is None:
return {}
data = self.params.get(method_id, {})
data = self.params.get(method_id)
if not data:
data = automation_params.get(method_id, {})
params = serializer(data).data
return {
field_name: automation_params.get(field_name, '')

View File

@ -0,0 +1,14 @@
- hosts: custom
gather_facts: no
vars:
ansible_connection: local
tasks:
- name: Test asset connection
ssh_ping:
login_user: "{{ jms_account.username }}"
login_password: "{{ jms_account.secret }}"
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
login_secret_type: "{{ jms_account.secret_type }}"
login_private_key_path: "{{ jms_account.private_key_path }}"

View File

@ -0,0 +1,8 @@
id: ping_by_ssh
name: Ping by SSH
category:
- device
- host
type:
- all
method: ping

View File

@ -14,8 +14,10 @@ class PingManager(BasePlaybookManager):
def method_type(cls):
return AutomationTypes.ping
def host_callback(self, host, asset=None, account=None, **kwargs):
super().host_callback(host, asset=asset, account=account, **kwargs)
def host_callback(self, host, asset=None, account=None, automation=None, **kwargs):
super().host_callback(
host, asset=asset, account=account, automation=automation, **kwargs
)
self.host_asset_and_account_mapper[host['name']] = (asset, account)
return host

View File

@ -32,15 +32,16 @@ class DeviceTypes(BaseType):
def _get_automation_constrains(cls) -> dict:
return {
'*': {
'ansible_enabled': False,
'ansible_enabled': True,
'ansible_config': {
'ansible_connection': 'local',
'first_conn_delay_time': 0.5,
},
'ping_enabled': False,
'ping_enabled': True,
'gather_facts_enabled': False,
'gather_accounts_enabled': False,
'verify_account_enabled': False,
'change_secret_enabled': False,
'verify_account_enabled': True,
'change_secret_enabled': True,
'push_account_enabled': False
}
}

View File

@ -150,7 +150,8 @@ class JMSInventory:
},
'jms_account': {
'id': str(account.id), 'username': account.username,
'secret': account.secret, 'secret_type': account.secret_type
'secret': account.secret, 'secret_type': account.secret_type,
'private_key_path': account.private_key_path
} if account else None
}

View File

@ -0,0 +1,115 @@
#!/usr/bin/python
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = '''
---
module: custom_command
short_description: Adds or removes a user with custom commands by ssh
description:
- You can add or edit users using ssh with custom commands.
options:
protocol:
default: ssh
choices: [ssh]
description:
- C(ssh) The remote asset is connected using ssh.
type: str
name:
description:
- The name of the user to add or remove.
required: true
aliases: [user]
type: str
password:
description:
- The password to use for the user.
type: str
aliases: [pass]
commands:
description:
- Custom change password commands.
type: list
required: true
first_conn_delay_time:
description:
- Delay for executing the command after SSH connection(unit: s)
type: float
required: false
'''
EXAMPLES = '''
- name: Create user with name 'jms' and password '123456'.
custom_command:
login_host: "localhost"
login_port: 22
login_user: "admin"
login_password: "123456"
name: "jms"
password: "123456"
commands: ['passwd {username}', '{password}', '{password}']
'''
RETURN = '''
name:
description: The name of the user to add.
returned: success
type: str
'''
from ansible.module_utils.basic import AnsibleModule
from ops.ansible.modules_utils.custom_common import (
SSHClient, ssh_common_argument_spec
)
def get_commands(module):
username = module.params['name']
password = module.params['password']
commands = module.params['commands'] or []
for index, command in enumerate(commands):
commands[index] = command.format(
username=username, password=password
)
return commands
# =========================================
# Module execution.
#
def main():
argument_spec = ssh_common_argument_spec()
argument_spec.update(
name=dict(required=True, aliases=['user']),
password=dict(aliases=['pass'], no_log=True),
commands=dict(type='list', required=False),
first_conn_delay_time=dict(
type='float', required=False, default=0.5
),
)
module = AnsibleModule(argument_spec=argument_spec)
ssh_client = SSHClient(module)
commands = get_commands(module)
if not commands:
module.fail_json(
msg='No command found, please go to the platform details to add'
)
err = ssh_client.execute(commands)
if err:
module.fail_json(
msg='There was a problem executing the command: %s' % err
)
user = module.params['name']
module.exit_json(changed=True, user=user)
if __name__ == '__main__':
main()

View File

@ -69,7 +69,7 @@ EXAMPLES = '''
oracle_user:
hostname: "remote server"
login_database: "helowin"
login_username: "system"
login_user: "system"
login_password: "123456"
name: "jms"
password: "123456"
@ -78,7 +78,7 @@ EXAMPLES = '''
oracle_user:
hostname: "remote server"
login_database: "helowin"
login_username: "system"
login_user: "system"
login_password: "123456"
name: "jms"
state: "absent"

View File

@ -0,0 +1,69 @@
#!/usr/bin/python
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = '''
---
module: custom_ssh_ping
short_description: Use ssh to probe whether an asset is connectable
description:
- Use ssh to probe whether an asset is connectable
'''
EXAMPLES = '''
- name: >
Ping asset server.
custom_ssh_ping:
login_host: 127.0.0.1
login_port: 22
login_user: jms
login_password: password
'''
RETURN = '''
is_available:
description: MongoDB server availability.
returned: always
type: bool
sample: true
conn_err_msg:
description: Connection error message.
returned: always
type: str
sample: ''
'''
from ansible.module_utils.basic import AnsibleModule
from ops.ansible.modules_utils.custom_common import (
SSHClient, ssh_common_argument_spec
)
# =========================================
# Module execution.
#
def main():
options = ssh_common_argument_spec()
module = AnsibleModule(argument_spec=options, supports_check_mode=True,)
result = {
'changed': False, 'is_available': True
}
client = SSHClient(module)
err = client.connect()
if err:
module.fail_json(msg='Unable to connect to asset: %s' % err)
result['is_available'] = False
return module.exit_json(**result)
if __name__ == '__main__':
main()

View File

@ -0,0 +1,68 @@
import time
import paramiko
from paramiko.ssh_exception import SSHException, NoValidConnectionsError
def ssh_common_argument_spec():
options = dict(
login_host=dict(type='str', required=False, default='localhost'),
login_port=dict(type='int', required=False, default=22),
login_user=dict(type='str', required=False, default='root'),
login_password=dict(type='str', required=False, no_log=True),
login_secret_type=dict(type='str', required=False, default='password'),
login_private_key_path=dict(type='str', required=False, no_log=True),
)
return options
class SSHClient:
def __init__(self, module):
self.module = module
self.is_connect = False
self.client = paramiko.SSHClient()
self.client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
def get_connect_params(self):
params = {
'allow_agent': False, 'look_for_keys': False,
'hostname': self.module.params['login_host'],
'port': self.module.params['login_port'],
'username': self.module.params['login_user'],
}
secret_type = self.module.params['login_secret_type']
if secret_type == 'ssh_key':
params['key_filename'] = self.module.params['login_private_key_path']
else:
params['password'] = self.module.params['login_password']
return params
def connect(self):
try:
self.client.connect(**self.get_connect_params())
except (SSHException, NoValidConnectionsError) as err:
err_msg = str(err)
else:
self.is_connect = True
err_msg = ''
return err_msg
def execute(self, commands):
if not self.is_connect:
self.connect()
channel = self.client.invoke_shell()
# 读取首次登陆终端返回的消息
channel.recv(2048)
# 网络设备一般登录有延迟,等终端有返回后再执行命令
delay_time = self.module.params['first_conn_delay_time']
time.sleep(delay_time)
err_msg = ''
try:
for command in commands:
channel.send(command + '\n')
time.sleep(0.3)
except SSHException as e:
err_msg = str(e)
return err_msg