Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support more scenarios #1

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 84 additions & 24 deletions action_plugins/synology_dsm_api_request.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,80 +5,140 @@
__metaclass__ = type

import urllib
import json

from ansible.plugins.action import ActionBase


class ActionModule(ActionBase):

TRANSFERS_FILES = True

PARAM_DEFAULTS = dict(
base_url = 'http://localhost:5000',
request_method = 'GET',
login_cookie = None,
login_user = None,
login_cookie = None,
login_user = None,
login_password = None,
cgi_path = '/webapi/',
cgi_name = 'entry.cgi',
api_name = None,
api_name = None,
api_version = '1',
api_method = None,
api_params = None,
api_method = None,
api_params = None,
request_json = None,
)

def run(self, tmp=None, task_vars=None):
self._supports_async = True

if task_vars is None:
task_vars = dict()

result = super(ActionModule, self).run(tmp, task_vars)
del tmp # tmp no longer has any effect
def extract_params(self):
"""return the params with none items removed
:return: a dictionary of the params, no Nones, and correctly prioritized to the user input
"""

# Build task args
# Capture the default params
task_args = self.PARAM_DEFAULTS.copy()

# Undo the json -> python-literal auto-transformation made by jinja2
# follow https://github.com/ansible/ansible/issues/68643#issue-592816310
if ("api_params") in self._task.args:
if ("compound") in self._task.args["api_params"]:
compound = self._task.args["api_params"]["compound"]
if (isinstance(compound, dict) or type(compound) == list):
json_str = json.dumps(compound)
self._task.args["api_params"]["compound"] = json_str
else:
# mostly an AnsibleUnicode type
# and jinja template didn't touch it
pass

# Overwrite the params with the input args
task_args.update(self._task.args)
# Remove the none items
for arg in task_args.keys():
if task_args[arg] is None:
del task_args[arg]

# Build 'uri' module params
if task_args[arg] is not None:
print("{}:\t {}".format(arg, type(task_args[arg])))
return {k: v for k, v in task_args.items() if v is not None}

def build_uri(task_args):
""" Create base_url/cgi_path/cgi_name and set the GET or POST method

:param dict task_args: the arguments of the request, including the url, path, method, ... etc.
:return: A uri representing the request, ie: http://localhost:5000/webapi/entry.cgi
It will include the username and password, at least for the first time
before having the login_cookie
"""
uri_params = dict(
url = "%s/%s/%s" % (task_args['base_url'], task_args['cgi_path'].strip('/'), task_args['cgi_name']),
method = task_args['request_method'],
)

if 'login_cookie' in task_args:
uri_params['headers'] = dict(Cookie = task_args['login_cookie'])

if task_args['request_method'] == 'POST':
if 'request_json' in task_args:
# fill json body
uri_params['body'] = task_args['request_json']
uri_params['body_format'] = 'json'
else:
# fill form-urlencoded body
tmp_body = dict(
api = task_args['api_name'],
api = task_args['api_name'],
version = task_args['api_version'],
method = task_args['api_method'],
method = task_args['api_method'],
)

if 'api_params' in task_args:
tmp_body.update(task_args['api_params'])

uri_params['body'] = tmp_body
uri_params['body_format'] = 'form-urlencoded'

elif task_args['request_method'] == 'GET':
uri_params['url'] += '?api=%s&version=%s&method=%s' % (task_args['api_name'], task_args['api_version'], task_args['api_method'])
uri_params['url'] += '?api=%s&version=%s&method=%s' %(
task_args['api_name'],
task_args['api_version'],
task_args['api_method']
)

# encode further API params into the URL
# ie: include the username and password for the login
if 'api_params' in task_args:
try:
uri_params['url'] += '&%s' % urllib.parse.urlencode(task_args['api_params'])
except AttributeError:
uri_params['url'] += '&%s' % urllib.urlencode(task_args['api_params'])

result = self._execute_module('uri', module_args=uri_params, task_vars=task_vars, wrap_async=self._task.async_val)
return uri_params

def is_request_failing(result):
return (result.get('failed', False) or
result.get('json', {}).get('success', None) == False or
result.get('json', {}).get('data', {}).get('has_fail', None) == True
)

def run(self, tmp=None, task_vars=None):
self._supports_async = True

if task_vars is None:
task_vars = dict()

result = super(ActionModule, self).run(tmp, task_vars)
del tmp # tmp no longer has any effect

# Build task args
task_args = self.extract_params()

# Build URI compliant with synology API
uri_params = ActionModule.build_uri(task_args)

result = self._execute_module('uri',
module_args=uri_params,
task_vars=task_vars,
wrap_async=self._task.async_val)

if not self._task.async_val:
self._remove_tmp_path(self._connection._shell.tmpdir)

if result.get('failed', False) or (result.get('json', {}).get('success', None) == False):
if ActionModule.is_request_failing(result):
result['failed'] = True

return result
7 changes: 7 additions & 0 deletions defaults/main/login.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
synology_dsm_login_user: admin_devops_user
synology_dsm_login_pass: dXNlX2Ffc3Ryb25nX3Bhc3N3b3JkX2xpa2VfdGhpcw==

# NOTE: for passwords, use a base64 hash, in example:
# ```echo -n use_a_strong_password_like_this | base64```

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same note as somewhere else. Why base64? That isn’t secure when checking into version control. Better to use Ansible Vault: https://docs.ansible.com/ansible/latest/user_guide/vault.html#use-encrypt-string-to-create-encrypted-variables-to-embed-in-yaml

If the action_plugin is the only place which needs it in base64 isn’t it possible to encode it in the python file?


14 changes: 9 additions & 5 deletions defaults/main.yml → defaults/main/main.yml
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
---
synology_dsm_host: '{{ inventory_hostname }}'
synology_dsm_base_url: http://{{ synology_dsm_host }}:5000
# Only overwrite when the user supplied below will get it's password changed!
# as it's password will be exposed while communicating to this URI
# Overwriting is only useful if the ssh configuration or the very initial configs
# will be managed through this script,
# however it's advisable that the consecutive steps be executed through 127.0.0.1
# with an ssh tunnel utilized
synology_dsm_host: 127.0.0.1
synology_dsm_port: 5000
synology_dsm_base_url: http://{{synology_dsm_host}}:{{synology_dsm_port}}
synology_dsm_base_path: webapi
synology_dsm_cgi_name: entry.cgi

synology_dsm_username: admin
synology_dsm_password: changeme

synology_dsm_ssh_enable: true
synology_dsm_ssh_port: 22
synology_dsm_telnet_enable: false
Expand Down
13 changes: 13 additions & 0 deletions defaults/main/notif_mail.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
synology_dsm_notif_mail_enable: true
synology_dsm_notif_mail_receipent: [email protected]
synology_dsm_notif_mail_prefix: "[SYNO_EMAIL_PREFIX]"
synology_dsm_notif_mail_server: smtp.gmail.com
synology_dsm_notif_mail_port: 587
synology_dsm_notif_mail_ssl: true
synology_dsm_notif_mail_oauth: false
synology_dsm_notif_mail_welcome_mail: false
synology_dsm_notif_mail_auth_enable: true
synology_dsm_notif_mail_auth_user: [email protected]
synology_dsm_notif_mail_auth_pass: YV9zdHJvbmdfcGFzc3dvcmRfbGlrZV90aGlz
synology_dsm_notif_mail_sender_name: Sender Name
synology_dsm_notif_mail_sender_mail: [email protected]
9 changes: 9 additions & 0 deletions defaults/main/user_create.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
synology_dsm_new_user_name: automated_user1

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not have this as e.g. a dict?

synology_dsm_users:
  automated_user1:
    description: Automatically created test user
    email: ""
    cnt_chg_pw: false
    expired: normal
    notify_by_email: false
    send_pw: false
    pass: dXNlX2Ffc3Ryb25nX3Bhc3N3b3JkX2xpa2VfdGhpcw==
  automated_user2:
    # etc

Multiple users can be specified this way.

synology_dsm_new_user_description: Automatically created test user
synology_dsm_new_user_email: ""
synology_dsm_new_user_cnt_chg_pw: false
synology_dsm_new_user_expired: normal
synology_dsm_new_user_notify_by_email: false
synology_dsm_new_user_send_pw: false
synology_dsm_new_user_pass: dXNlX2Ffc3Ryb25nX3Bhc3N3b3JkX2xpa2VfdGhpcw==
4 changes: 4 additions & 0 deletions defaults/main/user_delete.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
synology_dsm_users_to_delete:
- "to_be_deleted_user1"

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Preferably default this to an empty list:

synology_dsm_users_to_delete: []

- "to_be_deleted_user2"
Loading