mirror of
https://github.com/ansible-collections/community.general.git
synced 2026-02-04 16:01:55 +00:00
Sort imports with ruff check --fix (#11400)
Sort imports with ruff check --fix.
(cherry picked from commit 236b9c0e04)
Co-authored-by: Felix Fontein <felix@fontein.de>
605 lines
23 KiB
Python
605 lines
23 KiB
Python
#!/usr/bin/python
|
|
#
|
|
# PubNub Real-time Cloud-Hosted Push API and Push Notification Client
|
|
# Frameworks
|
|
# Copyright (C) 2016 PubNub Inc.
|
|
# http://www.pubnub.com/
|
|
# http://www.pubnub.com/terms
|
|
#
|
|
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
from __future__ import annotations
|
|
|
|
DOCUMENTATION = r"""
|
|
module: pubnub_blocks
|
|
short_description: PubNub blocks management module
|
|
description:
|
|
- 'This module allows Ansible to interface with the PubNub BLOCKS infrastructure by providing the following operations:
|
|
create / remove, start / stop and rename for blocks and create / modify / remove for event handlers.'
|
|
author:
|
|
- PubNub <support@pubnub.com> (@pubnub)
|
|
- Sergey Mamontov <sergey@pubnub.com> (@parfeon)
|
|
requirements:
|
|
- "pubnub_blocks_client >= 1.0"
|
|
extends_documentation_fragment:
|
|
- community.general.attributes
|
|
attributes:
|
|
check_mode:
|
|
support: full
|
|
diff_mode:
|
|
support: none
|
|
options:
|
|
email:
|
|
description:
|
|
- Email from account for which new session should be started.
|
|
- Not required if O(cache) contains result of previous module call (in same play).
|
|
type: str
|
|
default: ''
|
|
password:
|
|
description:
|
|
- Password which match to account to which specified O(email) belong.
|
|
- Not required if O(cache) contains result of previous module call (in same play).
|
|
type: str
|
|
default: ''
|
|
cache:
|
|
description: >-
|
|
In case if single play use blocks management module few times it is preferred to enabled 'caching' by making previous
|
|
module to share gathered artifacts and pass them to this parameter.
|
|
type: dict
|
|
default: {}
|
|
account:
|
|
description:
|
|
- Name of PubNub account for from which O(application) is used to manage blocks.
|
|
- User's account is used if value not set or empty.
|
|
type: str
|
|
default: ''
|
|
application:
|
|
description:
|
|
- Name of target PubNub application for which blocks configuration on specific O(keyset) is done.
|
|
type: str
|
|
required: true
|
|
keyset:
|
|
description:
|
|
- Name of application's keys set which is bound to managed blocks.
|
|
type: str
|
|
required: true
|
|
state:
|
|
description:
|
|
- Intended block state after event handlers creation / update process is completed.
|
|
default: 'present'
|
|
choices: ['started', 'stopped', 'present', 'absent']
|
|
type: str
|
|
name:
|
|
description:
|
|
- Name of managed block which is later visible on admin.pubnub.com.
|
|
required: true
|
|
type: str
|
|
description:
|
|
description:
|
|
- Short block description which is later visible on U(https://admin.pubnub.com).
|
|
- Used only if block does not exists and does not change description for existing block.
|
|
type: str
|
|
event_handlers:
|
|
description:
|
|
- List of event handlers which should be updated for specified block O(name).
|
|
- 'Each entry for new event handler should contain: V(name), V(src), V(channels), V(event). V(name) used as event handler
|
|
name which can be used later to make changes to it.'
|
|
- C(src) is full path to file with event handler code.
|
|
- V(channels) is name of channel from which event handler is waiting for events.
|
|
- 'V(event) is type of event which is able to trigger event handler: V(js-before-publish), V(js-after-publish), V(js-after-presence).'
|
|
- Each entry for existing handlers should contain C(name) (so target handler can be identified). Rest parameters (C(src),
|
|
C(channels) and C(event)) can be added if changes required for them.
|
|
- It is possible to rename event handler by adding C(changes) key to event handler payload and pass dictionary, which
|
|
contains single key C(name), where new name should be passed.
|
|
- To remove particular event handler it is possible to set C(state) for it to C(absent) and it is removed.
|
|
default: []
|
|
type: list
|
|
elements: dict
|
|
changes:
|
|
description:
|
|
- List of fields which should be changed by block itself (does not affect any event handlers).
|
|
- 'Possible options for change is: O(name).'
|
|
default: {}
|
|
type: dict
|
|
validate_certs:
|
|
description:
|
|
- This key allow to try skip certificates check when performing REST API calls. Sometimes host may have issues with
|
|
certificates on it and this causes problems to call PubNub REST API.
|
|
- If check should be ignored V(false) should be passed to this parameter.
|
|
default: true
|
|
type: bool
|
|
"""
|
|
|
|
EXAMPLES = r"""
|
|
# Event handler create example.
|
|
- name: Create single event handler
|
|
community.general.pubnub_blocks:
|
|
email: '{{ email }}'
|
|
password: '{{ password }}'
|
|
application: '{{ app_name }}'
|
|
keyset: '{{ keyset_name }}'
|
|
name: '{{ block_name }}'
|
|
event_handlers:
|
|
- src: '{{ path_to_handler_source }}'
|
|
name: '{{ handler_name }}'
|
|
event: 'js-before-publish'
|
|
channels: '{{ handler_channel }}'
|
|
|
|
# Change event handler trigger event type.
|
|
- name: Change event handler 'event'
|
|
community.general.pubnub_blocks:
|
|
email: '{{ email }}'
|
|
password: '{{ password }}'
|
|
application: '{{ app_name }}'
|
|
keyset: '{{ keyset_name }}'
|
|
name: '{{ block_name }}'
|
|
event_handlers:
|
|
- name: '{{ handler_name }}'
|
|
event: 'js-after-publish'
|
|
|
|
# Stop block and event handlers.
|
|
- name: Stopping block
|
|
community.general.pubnub_blocks:
|
|
email: '{{ email }}'
|
|
password: '{{ password }}'
|
|
application: '{{ app_name }}'
|
|
keyset: '{{ keyset_name }}'
|
|
name: '{{ block_name }}'
|
|
state: stop
|
|
|
|
# Multiple module calls with cached result passing
|
|
- name: Create '{{ block_name }}' block
|
|
register: module_cache
|
|
community.general.pubnub_blocks:
|
|
email: '{{ email }}'
|
|
password: '{{ password }}'
|
|
application: '{{ app_name }}'
|
|
keyset: '{{ keyset_name }}'
|
|
name: '{{ block_name }}'
|
|
state: present
|
|
- name: Add '{{ event_handler_1_name }}' handler to '{{ block_name }}'
|
|
register: module_cache
|
|
community.general.pubnub_blocks:
|
|
cache: '{{ module_cache }}'
|
|
application: '{{ app_name }}'
|
|
keyset: '{{ keyset_name }}'
|
|
name: '{{ block_name }}'
|
|
state: present
|
|
event_handlers:
|
|
- src: '{{ path_to_handler_1_source }}'
|
|
name: '{{ event_handler_1_name }}'
|
|
channels: '{{ event_handler_1_channel }}'
|
|
event: 'js-before-publish'
|
|
- name: Add '{{ event_handler_2_name }}' handler to '{{ block_name }}'
|
|
register: module_cache
|
|
community.general.pubnub_blocks:
|
|
cache: '{{ module_cache }}'
|
|
application: '{{ app_name }}'
|
|
keyset: '{{ keyset_name }}'
|
|
name: '{{ block_name }}'
|
|
state: present
|
|
event_handlers:
|
|
- src: '{{ path_to_handler_2_source }}'
|
|
name: '{{ event_handler_2_name }}'
|
|
channels: '{{ event_handler_2_channel }}'
|
|
event: 'js-before-publish'
|
|
- name: Start '{{ block_name }}' block
|
|
register: module_cache
|
|
community.general.pubnub_blocks:
|
|
cache: '{{ module_cache }}'
|
|
application: '{{ app_name }}'
|
|
keyset: '{{ keyset_name }}'
|
|
name: '{{ block_name }}'
|
|
state: started
|
|
"""
|
|
|
|
RETURN = r"""
|
|
module_cache:
|
|
description:
|
|
- Cached account information. In case if with single play module used few times it is better to pass cached data to next
|
|
module calls to speed up process.
|
|
type: dict
|
|
returned: always
|
|
"""
|
|
import copy
|
|
import os
|
|
|
|
try:
|
|
# Import PubNub BLOCKS client.
|
|
from pubnub_blocks_client import Block, EventHandler, User, exceptions
|
|
|
|
HAS_PUBNUB_BLOCKS_CLIENT = True
|
|
except ImportError:
|
|
HAS_PUBNUB_BLOCKS_CLIENT = False
|
|
User = None
|
|
Account = None
|
|
Owner = None
|
|
Application = None
|
|
Keyset = None
|
|
Block = None
|
|
EventHandler = None
|
|
exceptions = None
|
|
|
|
from ansible.module_utils.basic import AnsibleModule
|
|
from ansible.module_utils.common.text.converters import to_text
|
|
|
|
|
|
def pubnub_user(module):
|
|
"""Create and configure user model if it possible.
|
|
|
|
:type module: AnsibleModule
|
|
:param module: Reference on module which contain module launch
|
|
information and status report methods.
|
|
|
|
:rtype: User
|
|
:return: Reference on initialized and ready to use user or 'None' in
|
|
case if not all required information has been passed to block.
|
|
"""
|
|
user = None
|
|
params = module.params
|
|
|
|
if params.get("cache") and params["cache"].get("module_cache"):
|
|
cache = params["cache"]["module_cache"]
|
|
user = User()
|
|
user.restore(cache=copy.deepcopy(cache["pnm_user"]))
|
|
elif params.get("email") and params.get("password"):
|
|
user = User(email=params.get("email"), password=params.get("password"))
|
|
else:
|
|
err_msg = (
|
|
"It looks like not account credentials has been passed or "
|
|
"'cache' field doesn't have result of previous module "
|
|
"call."
|
|
)
|
|
module.fail_json(msg="Missing account credentials.", description=err_msg, changed=False)
|
|
|
|
return user
|
|
|
|
|
|
def pubnub_account(module, user):
|
|
"""Create and configure account if it is possible.
|
|
|
|
:type module: AnsibleModule
|
|
:param module: Reference on module which contain module launch
|
|
information and status report methods.
|
|
:type user: User
|
|
:param user: Reference on authorized user for which one of accounts
|
|
should be used during manipulations with block.
|
|
|
|
:rtype: Account
|
|
:return: Reference on initialized and ready to use account or 'None' in
|
|
case if not all required information has been passed to block.
|
|
"""
|
|
params = module.params
|
|
if params.get("account"):
|
|
account_name = params.get("account")
|
|
account = user.account(name=params.get("account"))
|
|
if account is None:
|
|
err_frmt = (
|
|
"It looks like there is no '{0}' account for "
|
|
"authorized user. Please make sure what correct "
|
|
"name has been passed during module configuration."
|
|
)
|
|
module.fail_json(msg="Missing account.", description=err_frmt.format(account_name), changed=False)
|
|
else:
|
|
account = user.accounts()[0]
|
|
|
|
return account
|
|
|
|
|
|
def pubnub_application(module, account):
|
|
"""Retrieve reference on target application from account model.
|
|
|
|
NOTE: In case if account authorization will fail or there is no
|
|
application with specified name, module will exit with error.
|
|
:type module: AnsibleModule
|
|
:param module: Reference on module which contain module launch
|
|
information and status report methods.
|
|
:type account: Account
|
|
:param account: Reference on PubNub account model from which reference
|
|
on application should be fetched.
|
|
|
|
:rtype: Application
|
|
:return: Reference on initialized and ready to use application model.
|
|
"""
|
|
application = None
|
|
params = module.params
|
|
try:
|
|
application = account.application(params["application"])
|
|
except (exceptions.AccountError, exceptions.GeneralPubNubError) as exc:
|
|
exc_msg = _failure_title_from_exception(exc)
|
|
exc_descr = exc.message if hasattr(exc, "message") else exc.args[0]
|
|
module.fail_json(msg=exc_msg, description=exc_descr, changed=account.changed, module_cache=dict(account))
|
|
|
|
if application is None:
|
|
err_fmt = (
|
|
"There is no '{0}' application for {1}. Make sure what "
|
|
"correct application name has been passed. If application "
|
|
"doesn't exist you can create it on admin.pubnub.com."
|
|
)
|
|
email = account.owner.email
|
|
module.fail_json(
|
|
msg=err_fmt.format(params["application"], email), changed=account.changed, module_cache=dict(account)
|
|
)
|
|
|
|
return application
|
|
|
|
|
|
def pubnub_keyset(module, account, application):
|
|
"""Retrieve reference on target keyset from application model.
|
|
|
|
NOTE: In case if there is no keyset with specified name, module will
|
|
exit with error.
|
|
:type module: AnsibleModule
|
|
:param module: Reference on module which contain module launch
|
|
information and status report methods.
|
|
:type account: Account
|
|
:param account: Reference on PubNub account model which will be
|
|
used in case of error to export cached data.
|
|
:type application: Application
|
|
:param application: Reference on PubNub application model from which
|
|
reference on keyset should be fetched.
|
|
|
|
:rtype: Keyset
|
|
:return: Reference on initialized and ready to use keyset model.
|
|
"""
|
|
params = module.params
|
|
keyset = application.keyset(params["keyset"])
|
|
if keyset is None:
|
|
err_fmt = (
|
|
"There is no '{0}' keyset for '{1}' application. Make "
|
|
"sure what correct keyset name has been passed. If keyset "
|
|
"doesn't exist you can create it on admin.pubnub.com."
|
|
)
|
|
module.fail_json(
|
|
msg=err_fmt.format(params["keyset"], application.name), changed=account.changed, module_cache=dict(account)
|
|
)
|
|
|
|
return keyset
|
|
|
|
|
|
def pubnub_block(module, account, keyset):
|
|
"""Retrieve reference on target keyset from application model.
|
|
|
|
NOTE: In case if there is no block with specified name and module
|
|
configured to start/stop it, module will exit with error.
|
|
:type module: AnsibleModule
|
|
:param module: Reference on module which contain module launch
|
|
information and status report methods.
|
|
:type account: Account
|
|
:param account: Reference on PubNub account model which will be used in
|
|
case of error to export cached data.
|
|
:type keyset: Keyset
|
|
:param keyset: Reference on keyset model from which reference on block
|
|
should be fetched.
|
|
|
|
:rtype: Block
|
|
:return: Reference on initialized and ready to use keyset model.
|
|
"""
|
|
block = None
|
|
params = module.params
|
|
try:
|
|
block = keyset.block(params["name"])
|
|
except (exceptions.KeysetError, exceptions.GeneralPubNubError) as exc:
|
|
exc_msg = _failure_title_from_exception(exc)
|
|
exc_descr = exc.message if hasattr(exc, "message") else exc.args[0]
|
|
module.fail_json(msg=exc_msg, description=exc_descr, changed=account.changed, module_cache=dict(account))
|
|
|
|
# Report error because block doesn't exists and at the same time
|
|
# requested to start/stop.
|
|
if block is None and params["state"] in ["started", "stopped"]:
|
|
block_name = params.get("name")
|
|
module.fail_json(
|
|
msg=f"'{block_name}' block doesn't exists.", changed=account.changed, module_cache=dict(account)
|
|
)
|
|
|
|
if block is None and params["state"] == "present":
|
|
block = Block(name=params.get("name"), description=params.get("description"))
|
|
keyset.add_block(block)
|
|
|
|
if block:
|
|
# Update block information if required.
|
|
if params.get("changes") and params["changes"].get("name"):
|
|
block.name = params["changes"]["name"]
|
|
if params.get("description"):
|
|
block.description = params.get("description")
|
|
|
|
return block
|
|
|
|
|
|
def pubnub_event_handler(block, data):
|
|
"""Retrieve reference on target event handler from application model.
|
|
|
|
:type block: Block
|
|
:param block: Reference on block model from which reference on event
|
|
handlers should be fetched.
|
|
:type data: dict
|
|
:param data: Reference on dictionary which contain information about
|
|
event handler and whether it should be created or not.
|
|
|
|
:rtype: EventHandler
|
|
:return: Reference on initialized and ready to use event handler model.
|
|
'None' will be returned in case if there is no handler with
|
|
specified name and no request to create it.
|
|
"""
|
|
event_handler = block.event_handler(data["name"])
|
|
|
|
# Prepare payload for event handler update.
|
|
changed_name = data.pop("changes").get("name") if "changes" in data else None
|
|
name = data.get("name") or changed_name
|
|
channels = data.get("channels")
|
|
event = data.get("event")
|
|
code = _content_of_file_at_path(data.get("src"))
|
|
state = data.get("state") or "present"
|
|
|
|
# Create event handler if required.
|
|
if event_handler is None and state == "present":
|
|
event_handler = EventHandler(name=name, channels=channels, event=event, code=code)
|
|
block.add_event_handler(event_handler)
|
|
|
|
# Update event handler if required.
|
|
if event_handler is not None and state == "present":
|
|
if name is not None:
|
|
event_handler.name = name
|
|
if channels is not None:
|
|
event_handler.channels = channels
|
|
if event is not None:
|
|
event_handler.event = event
|
|
if code is not None:
|
|
event_handler.code = code
|
|
|
|
return event_handler
|
|
|
|
|
|
def _failure_title_from_exception(exception):
|
|
"""Compose human-readable title for module error title.
|
|
|
|
Title will be based on status codes if they has been provided.
|
|
:type exception: exceptions.GeneralPubNubError
|
|
:param exception: Reference on exception for which title should be
|
|
composed.
|
|
|
|
:rtype: str
|
|
:return: Reference on error tile which should be shown on module
|
|
failure.
|
|
"""
|
|
title = "General REST API access error."
|
|
if exception.code == exceptions.PN_AUTHORIZATION_MISSING_CREDENTIALS:
|
|
title = "Authorization error: missing credentials."
|
|
elif exception.code == exceptions.PN_AUTHORIZATION_WRONG_CREDENTIALS:
|
|
title = "Authorization error: wrong credentials."
|
|
elif exception.code == exceptions.PN_USER_INSUFFICIENT_RIGHTS:
|
|
title = "API access error: insufficient access rights."
|
|
elif exception.code == exceptions.PN_API_ACCESS_TOKEN_EXPIRED:
|
|
title = "API access error: time token expired."
|
|
elif exception.code == exceptions.PN_KEYSET_BLOCK_EXISTS:
|
|
title = "Block create did fail: block with same name already exists)."
|
|
elif exception.code == exceptions.PN_KEYSET_BLOCKS_FETCH_DID_FAIL:
|
|
title = "Unable fetch list of blocks for keyset."
|
|
elif exception.code == exceptions.PN_BLOCK_CREATE_DID_FAIL:
|
|
title = "Block creation did fail."
|
|
elif exception.code == exceptions.PN_BLOCK_UPDATE_DID_FAIL:
|
|
title = "Block update did fail."
|
|
elif exception.code == exceptions.PN_BLOCK_REMOVE_DID_FAIL:
|
|
title = "Block removal did fail."
|
|
elif exception.code == exceptions.PN_BLOCK_START_STOP_DID_FAIL:
|
|
title = "Block start/stop did fail."
|
|
elif exception.code == exceptions.PN_EVENT_HANDLER_MISSING_FIELDS:
|
|
title = "Event handler creation did fail: missing fields."
|
|
elif exception.code == exceptions.PN_BLOCK_EVENT_HANDLER_EXISTS:
|
|
title = "Event handler creation did fail: missing fields."
|
|
elif exception.code == exceptions.PN_EVENT_HANDLER_CREATE_DID_FAIL:
|
|
title = "Event handler creation did fail."
|
|
elif exception.code == exceptions.PN_EVENT_HANDLER_UPDATE_DID_FAIL:
|
|
title = "Event handler update did fail."
|
|
elif exception.code == exceptions.PN_EVENT_HANDLER_REMOVE_DID_FAIL:
|
|
title = "Event handler removal did fail."
|
|
|
|
return title
|
|
|
|
|
|
def _content_of_file_at_path(path):
|
|
"""Read file content.
|
|
|
|
Try read content of file at specified path.
|
|
:type path: str
|
|
:param path: Full path to location of file which should be read'ed.
|
|
:rtype: content
|
|
:return: File content or 'None'
|
|
"""
|
|
content = None
|
|
if path and os.path.exists(path):
|
|
with open(path) as opened_file:
|
|
b_content = opened_file.read()
|
|
try:
|
|
content = to_text(b_content, errors="surrogate_or_strict")
|
|
except UnicodeError:
|
|
pass
|
|
|
|
return content
|
|
|
|
|
|
def main():
|
|
fields = dict(
|
|
email=dict(default="", type="str"),
|
|
password=dict(default="", type="str", no_log=True),
|
|
account=dict(default="", type="str"),
|
|
application=dict(required=True, type="str"),
|
|
keyset=dict(required=True, type="str", no_log=False),
|
|
state=dict(default="present", type="str", choices=["started", "stopped", "present", "absent"]),
|
|
name=dict(required=True, type="str"),
|
|
description=dict(type="str"),
|
|
event_handlers=dict(default=list(), type="list", elements="dict"),
|
|
changes=dict(default=dict(), type="dict"),
|
|
cache=dict(default=dict(), type="dict"),
|
|
validate_certs=dict(default=True, type="bool"),
|
|
)
|
|
module = AnsibleModule(argument_spec=fields, supports_check_mode=True)
|
|
|
|
if not HAS_PUBNUB_BLOCKS_CLIENT:
|
|
module.fail_json(msg="pubnub_blocks_client required for this module.")
|
|
|
|
params = module.params
|
|
|
|
# Authorize user.
|
|
user = pubnub_user(module)
|
|
# Initialize PubNub account instance.
|
|
account = pubnub_account(module, user=user)
|
|
# Try fetch application with which module should work.
|
|
application = pubnub_application(module, account=account)
|
|
# Try fetch keyset with which module should work.
|
|
keyset = pubnub_keyset(module, account=account, application=application)
|
|
# Try fetch block with which module should work.
|
|
block = pubnub_block(module, account=account, keyset=keyset)
|
|
is_new_block = block is not None and block.uid == -1
|
|
|
|
# Check whether block should be removed or not.
|
|
if block is not None and params["state"] == "absent":
|
|
keyset.remove_block(block)
|
|
block = None
|
|
|
|
if block is not None:
|
|
# Update block information if required.
|
|
if params.get("changes") and params["changes"].get("name"):
|
|
block.name = params["changes"]["name"]
|
|
|
|
# Process event changes to event handlers.
|
|
for event_handler_data in params.get("event_handlers") or list():
|
|
state = event_handler_data.get("state") or "present"
|
|
event_handler = pubnub_event_handler(data=event_handler_data, block=block)
|
|
if state == "absent" and event_handler:
|
|
block.delete_event_handler(event_handler)
|
|
|
|
# Update block operation state if required.
|
|
if block and not is_new_block:
|
|
if params["state"] == "started":
|
|
block.start()
|
|
elif params["state"] == "stopped":
|
|
block.stop()
|
|
|
|
# Save current account state.
|
|
if not module.check_mode:
|
|
try:
|
|
account.save()
|
|
except (
|
|
exceptions.APIAccessError,
|
|
exceptions.KeysetError,
|
|
exceptions.BlockError,
|
|
exceptions.EventHandlerError,
|
|
exceptions.GeneralPubNubError,
|
|
) as exc:
|
|
module_cache = dict(account)
|
|
module_cache.update(dict(pnm_user=dict(user)))
|
|
exc_msg = _failure_title_from_exception(exc)
|
|
exc_descr = exc.message if hasattr(exc, "message") else exc.args[0]
|
|
module.fail_json(msg=exc_msg, description=exc_descr, changed=account.changed, module_cache=module_cache)
|
|
|
|
# Report module execution results.
|
|
module_cache = dict(account)
|
|
module_cache.update(dict(pnm_user=dict(user)))
|
|
changed_will_change = account.changed or account.will_change
|
|
module.exit_json(changed=changed_will_change, module_cache=module_cache)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|