1#!/usr/local/bin/python3.8
2# -*- coding: utf-8 -*-
3
4# Copyright (c) 2016, Thierno IB. BARRY @barryib
5# Sponsored by Polyconseil http://polyconseil.fr.
6#
7# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
8
9from __future__ import absolute_import, division, print_function
10__metaclass__ = type
11
12
13DOCUMENTATION = '''
14---
15module: kibana_plugin
16short_description: Manage Kibana plugins
17description:
18    - This module can be used to manage Kibana plugins.
19author: Thierno IB. BARRY (@barryib)
20options:
21    name:
22      description:
23      - Name of the plugin to install.
24      required: True
25      type: str
26    state:
27      description:
28      - Desired state of a plugin.
29      choices: ["present", "absent"]
30      default: present
31      type: str
32    url:
33      description:
34      - Set exact URL to download the plugin from.
35      - For local file, prefix its absolute path with file://
36      type: str
37    timeout:
38      description:
39      - "Timeout setting: 30s, 1m, 1h etc."
40      default: 1m
41      type: str
42    plugin_bin:
43      description:
44      - Location of the Kibana binary.
45      default: /opt/kibana/bin/kibana
46      type: path
47    plugin_dir:
48      description:
49      - Your configured plugin directory specified in Kibana.
50      default: /opt/kibana/installedPlugins/
51      type: path
52    version:
53      description:
54      - Version of the plugin to be installed.
55      - If plugin exists with previous version, plugin will NOT be updated unless C(force) is set to yes.
56      type: str
57    force:
58      description:
59      - Delete and re-install the plugin. Can be useful for plugins update.
60      type: bool
61      default: false
62    allow_root:
63      description:
64      - Whether to allow C(kibana) and C(kibana-plugin) to be run as root. Passes the C(--allow-root) flag to these commands.
65      type: bool
66      default: false
67      version_added: 2.3.0
68'''
69
70EXAMPLES = '''
71- name: Install Elasticsearch head plugin
72  community.general.kibana_plugin:
73    state: present
74    name: elasticsearch/marvel
75
76- name: Install specific version of a plugin
77  community.general.kibana_plugin:
78    state: present
79    name: elasticsearch/marvel
80    version: '2.3.3'
81
82- name: Uninstall Elasticsearch head plugin
83  community.general.kibana_plugin:
84    state: absent
85    name: elasticsearch/marvel
86'''
87
88RETURN = '''
89cmd:
90    description: the launched command during plugin management (install / remove)
91    returned: success
92    type: str
93name:
94    description: the plugin name to install or remove
95    returned: success
96    type: str
97url:
98    description: the url from where the plugin is installed from
99    returned: success
100    type: str
101timeout:
102    description: the timeout for plugin download
103    returned: success
104    type: str
105stdout:
106    description: the command stdout
107    returned: success
108    type: str
109stderr:
110    description: the command stderr
111    returned: success
112    type: str
113state:
114    description: the state for the managed plugin
115    returned: success
116    type: str
117'''
118
119import os
120from distutils.version import LooseVersion
121from ansible.module_utils.basic import AnsibleModule
122
123
124PACKAGE_STATE_MAP = dict(
125    present="--install",
126    absent="--remove"
127)
128
129
130def parse_plugin_repo(string):
131    elements = string.split("/")
132
133    # We first consider the simplest form: pluginname
134    repo = elements[0]
135
136    # We consider the form: username/pluginname
137    if len(elements) > 1:
138        repo = elements[1]
139
140    # remove elasticsearch- prefix
141    # remove es- prefix
142    for string in ("elasticsearch-", "es-"):
143        if repo.startswith(string):
144            return repo[len(string):]
145
146    return repo
147
148
149def is_plugin_present(plugin_dir, working_dir):
150    return os.path.isdir(os.path.join(working_dir, plugin_dir))
151
152
153def parse_error(string):
154    reason = "reason: "
155    try:
156        return string[string.index(reason) + len(reason):].strip()
157    except ValueError:
158        return string
159
160
161def install_plugin(module, plugin_bin, plugin_name, url, timeout, allow_root, kibana_version='4.6'):
162    if LooseVersion(kibana_version) > LooseVersion('4.6'):
163        kibana_plugin_bin = os.path.join(os.path.dirname(plugin_bin), 'kibana-plugin')
164        cmd_args = [kibana_plugin_bin, "install"]
165        if url:
166            cmd_args.append(url)
167        else:
168            cmd_args.append(plugin_name)
169    else:
170        cmd_args = [plugin_bin, "plugin", PACKAGE_STATE_MAP["present"], plugin_name]
171
172        if url:
173            cmd_args.extend(["--url", url])
174
175    if timeout:
176        cmd_args.extend(["--timeout", timeout])
177
178    if allow_root:
179        cmd_args.append('--allow-root')
180
181    if module.check_mode:
182        return True, " ".join(cmd_args), "check mode", ""
183
184    rc, out, err = module.run_command(cmd_args)
185    if rc != 0:
186        reason = parse_error(out)
187        module.fail_json(msg=reason)
188
189    return True, " ".join(cmd_args), out, err
190
191
192def remove_plugin(module, plugin_bin, plugin_name, allow_root, kibana_version='4.6'):
193    if LooseVersion(kibana_version) > LooseVersion('4.6'):
194        kibana_plugin_bin = os.path.join(os.path.dirname(plugin_bin), 'kibana-plugin')
195        cmd_args = [kibana_plugin_bin, "remove", plugin_name]
196    else:
197        cmd_args = [plugin_bin, "plugin", PACKAGE_STATE_MAP["absent"], plugin_name]
198
199    if allow_root:
200        cmd_args.append('--allow-root')
201
202    if module.check_mode:
203        return True, " ".join(cmd_args), "check mode", ""
204
205    rc, out, err = module.run_command(cmd_args)
206    if rc != 0:
207        reason = parse_error(out)
208        module.fail_json(msg=reason)
209
210    return True, " ".join(cmd_args), out, err
211
212
213def get_kibana_version(module, plugin_bin, allow_root):
214    cmd_args = [plugin_bin, '--version']
215
216    if allow_root:
217        cmd_args.append('--allow-root')
218
219    rc, out, err = module.run_command(cmd_args)
220    if rc != 0:
221        module.fail_json(msg="Failed to get Kibana version : %s" % err)
222
223    return out.strip()
224
225
226def main():
227    module = AnsibleModule(
228        argument_spec=dict(
229            name=dict(required=True),
230            state=dict(default="present", choices=list(PACKAGE_STATE_MAP.keys())),
231            url=dict(default=None),
232            timeout=dict(default="1m"),
233            plugin_bin=dict(default="/opt/kibana/bin/kibana", type="path"),
234            plugin_dir=dict(default="/opt/kibana/installedPlugins/", type="path"),
235            version=dict(default=None),
236            force=dict(default=False, type="bool"),
237            allow_root=dict(default=False, type="bool"),
238        ),
239        supports_check_mode=True,
240    )
241
242    name = module.params["name"]
243    state = module.params["state"]
244    url = module.params["url"]
245    timeout = module.params["timeout"]
246    plugin_bin = module.params["plugin_bin"]
247    plugin_dir = module.params["plugin_dir"]
248    version = module.params["version"]
249    force = module.params["force"]
250    allow_root = module.params["allow_root"]
251
252    changed, cmd, out, err = False, '', '', ''
253
254    kibana_version = get_kibana_version(module, plugin_bin, allow_root)
255
256    present = is_plugin_present(parse_plugin_repo(name), plugin_dir)
257
258    # skip if the state is correct
259    if (present and state == "present" and not force) or (state == "absent" and not present and not force):
260        module.exit_json(changed=False, name=name, state=state)
261
262    if version:
263        name = name + '/' + version
264
265    if state == "present":
266        if force:
267            remove_plugin(module, plugin_bin, name, allow_root, kibana_version)
268        changed, cmd, out, err = install_plugin(module, plugin_bin, name, url, timeout, allow_root, kibana_version)
269
270    elif state == "absent":
271        changed, cmd, out, err = remove_plugin(module, plugin_bin, name, allow_root, kibana_version)
272
273    module.exit_json(changed=changed, cmd=cmd, name=name, state=state, url=url, timeout=timeout, stdout=out, stderr=err)
274
275
276if __name__ == '__main__':
277    main()
278