178 lines
5.9 KiB
Python
Executable File
178 lines
5.9 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
|
|
# Copyright: (c) 2019, Alexandre Iooss <erdnaxe@crans.org>
|
|
#
|
|
# GNU General Public License v3.0+
|
|
|
|
import re
|
|
import urllib.error
|
|
import urllib.parse
|
|
import urllib.request
|
|
import difflib
|
|
|
|
|
|
from ansible.errors import AnsibleError
|
|
from ansible.plugins.action import ActionBase
|
|
from ansible.utils.display import Display
|
|
from ansible.module_utils._text import to_native
|
|
|
|
display = Display()
|
|
|
|
|
|
class ActionModule(ActionBase):
|
|
|
|
TRANSFERS_FILES = False
|
|
_VALID_ARGS = frozenset(('url', 'user', 'password', 'content', 'revision_comment'))
|
|
|
|
def login(self, url, user, password):
|
|
"""
|
|
Log in and return session cookie or None if failed
|
|
|
|
:param url: random wiki url (not root page)
|
|
:param user: wiki user
|
|
:param password: user's password
|
|
:return: session cookie
|
|
"""
|
|
# Send a HTTP POST request
|
|
data = urllib.parse.urlencode({
|
|
'action': 'login',
|
|
'login': 'Connexion',
|
|
'name': user,
|
|
'password': password
|
|
}).encode()
|
|
req = urllib.request.Request(url, data)
|
|
try:
|
|
response = urllib.request.urlopen(req)
|
|
cookie = response.getheader('set-cookie')
|
|
except urllib.error.HTTPError as e:
|
|
# If 404, then also return header
|
|
cookie = e.getheader('set-cookie')
|
|
|
|
# Check that authentication worked
|
|
if not cookie:
|
|
raise AnsibleError(to_native('server did not return a session cookie'))
|
|
return cookie
|
|
|
|
def craft_request(self, suffix):
|
|
"""
|
|
Crafts a function that takes an url and a cookie,
|
|
and returns the content of the requested page with given action suffix.
|
|
"""
|
|
def f(url, cookie):
|
|
req = urllib.request.Request(url + suffix)
|
|
req.add_header("Cookie", cookie)
|
|
content = urllib.request.urlopen(req).read().decode('utf-8')
|
|
return content
|
|
return f
|
|
|
|
|
|
def edit_ticket(self, url, cookie):
|
|
"""
|
|
Return edition ticket of url
|
|
|
|
:param url: page to edit
|
|
:param cookie: session cookie
|
|
:return: edit ticket
|
|
"""
|
|
# Send request with session cookie
|
|
content = self.craft_request("?action=edit&editor=text")(url, cookie)
|
|
|
|
# Search for ticket
|
|
search = re.search('name=\"ticket\" value=\"([^\"]*)\"', content)
|
|
if not search:
|
|
raise AnsibleError(to_native('no edit ticket was found'))
|
|
|
|
return search.group(1)
|
|
|
|
|
|
def edit(self, url, user, password, content, revision_comment, cookie):
|
|
"""
|
|
Edit a MoinMoin wiki page
|
|
|
|
:param url: page to edit
|
|
:param user: wiki user
|
|
:param password: user's password
|
|
:param content: content to place on this page
|
|
:param revision_comment: revision comment
|
|
"""
|
|
# Connect and get edit ticket
|
|
ticket = self.edit_ticket(url, cookie)
|
|
|
|
# Create request and send
|
|
data = {
|
|
'button_save': 'Enregistrer les modifications',
|
|
'category': '',
|
|
'comment': revision_comment.encode("utf-8"),
|
|
'savetext': content.encode("utf-8"),
|
|
'action': 'edit',
|
|
'ticket': ticket
|
|
}
|
|
req = urllib.request.Request(url, urllib.parse.urlencode(data).encode())
|
|
req.add_header("Cookie", cookie)
|
|
urllib.request.urlopen(req)
|
|
|
|
|
|
def run(self, tmp=None, task_vars=None):
|
|
"""
|
|
The run method is the main Action Plugin driver. All work is done from within this method.
|
|
|
|
tmp: Temporary directory. Sometimes an action plugin sets up
|
|
a temporary directory and then calls another module. This parameter
|
|
allows us to reuse the same directory for both.
|
|
|
|
task_vars: The variables (host vars, group vars, config vars, etc) associated with this task.
|
|
Note that while this will contain Ansible facts from the host, they should be used
|
|
with caution as a user running Ansible can disable their collection. If you want
|
|
make sure that your Action Plugin always has access to the ones it needs, you may
|
|
want to consider running the setup module directly in the run the method and getting
|
|
the Ansible facts that way.
|
|
The strategy plugin which manages running tasks on instances uses an ansible.vars.manager
|
|
VariableManager instance to retrieve this context specific dict of variables.
|
|
"""
|
|
if task_vars is None:
|
|
task_vars = dict()
|
|
|
|
|
|
result = super(ActionModule, self).run(tmp, task_vars)
|
|
del tmp
|
|
|
|
|
|
url = self._task.args.get("url")
|
|
user = self._task.args.get("user")
|
|
password = self._task.args.get("password")
|
|
content = self._task.args.get("content")
|
|
revision_comment = self._task.args.get("revision_comment")
|
|
|
|
cookie = self.login(url, user, password)
|
|
|
|
try:
|
|
raw = self.craft_request("?action=raw")(url, cookie)
|
|
except urllib.error.HTTPError: # We will create the page.
|
|
raw = ""
|
|
|
|
diff = difflib.unified_diff(raw.splitlines(), content.splitlines(), fromfile="old", tofile="new", lineterm="")
|
|
i=0
|
|
|
|
# Display any change
|
|
for line in diff:
|
|
i+=1
|
|
if line.startswith("-"):
|
|
display.display(line, "red")
|
|
elif line.startswith("+"):
|
|
display.display(line, "green")
|
|
elif line.startswith("@"):
|
|
display.display(line, "yellow")
|
|
else:
|
|
display.display(line)
|
|
|
|
# Do apply the change if not in check mode
|
|
if not self._play_context.check_mode:
|
|
self.edit(url, user, password, content, revision_comment, cookie)
|
|
|
|
result['changed']=i>0
|
|
|
|
self._supports_check_mode = True
|
|
self._supports_async = False
|
|
|
|
return result
|