1#!/usr/bin/python
2# -*- coding: utf-8 -*-
3#
4# Copyright: (c) 2018, F5 Networks Inc.
5# GNU General Public License v3.0 (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
6
7from __future__ import absolute_import, division, print_function
8__metaclass__ = type
9
10
11ANSIBLE_METADATA = {'metadata_version': '1.1',
12                    'status': ['preview'],
13                    'supported_by': 'certified'}
14
15DOCUMENTATION = r'''
16---
17module: bigip_apm_policy_import
18short_description: Manage BIG-IP APM policy or APM access profile imports
19description:
20   - Manage BIG-IP APM policy or APM access profile imports.
21version_added: 2.8
22options:
23  name:
24    description:
25      - The name of the APM policy or APM access profile to create or override.
26    type: str
27    required: True
28  type:
29    description:
30      - Specifies the type of item to export from device.
31    type: str
32    choices:
33      - profile_access
34      - access_policy
35    default: profile_access
36  source:
37    description:
38      - Full path to a file to be imported into the BIG-IP APM.
39    type: path
40  force:
41    description:
42      - When set to C(yes) any existing policy with the same name will be overwritten by the new import.
43      - If policy does not exist this setting is ignored.
44    default: no
45    type: bool
46  partition:
47    description:
48      - Device partition to manage resources on.
49    type: str
50    default: Common
51notes:
52  - Due to ID685681 it is not possible to execute ng_* tools via REST api on v12.x and 13.x, once this is fixed
53    this restriction will be removed.
54  - Requires BIG-IP >= 14.0.0
55extends_documentation_fragment: f5
56author:
57  - Wojciech Wypior (@wojtek0806)
58'''
59
60EXAMPLES = r'''
61- name: Import APM profile
62  bigip_apm_policy_import:
63    name: new_apm_profile
64    source: /root/apm_profile.tar.gz
65    provider:
66      server: lb.mydomain.com
67      user: admin
68      password: secret
69  delegate_to: localhost
70
71- name: Import APM policy
72  bigip_apm_policy_import:
73    name: new_apm_policy
74    source: /root/apm_policy.tar.gz
75    type: access_policy
76    provider:
77      server: lb.mydomain.com
78      user: admin
79      password: secret
80  delegate_to: localhost
81
82- name: Override existing APM policy
83  bigip_asm_policy:
84    name: new_apm_policy
85    source: /root/apm_policy.tar.gz
86    force: yes
87    provider:
88      server: lb.mydomain.com
89      user: admin
90      password: secret
91  delegate_to: localhost
92'''
93
94RETURN = r'''
95source:
96  description: Local path to APM policy file.
97  returned: changed
98  type: str
99  sample: /root/some_policy.tar.gz
100name:
101  description: Name of the APM policy or APM access profile to be created/overwritten.
102  returned: changed
103  type: str
104  sample: APM_policy_global
105type:
106  description: Set to specify type of item to export.
107  returned: changed
108  type: str
109  sample: access_policy
110force:
111  description: Set when overwriting an existing policy or profile.
112  returned: changed
113  type: bool
114  sample: yes
115'''
116
117import os
118from ansible.module_utils.basic import AnsibleModule
119from ansible.module_utils.basic import env_fallback
120from distutils.version import LooseVersion
121
122try:
123    from library.module_utils.network.f5.bigip import F5RestClient
124    from library.module_utils.network.f5.common import F5ModuleError
125    from library.module_utils.network.f5.common import AnsibleF5Parameters
126    from library.module_utils.network.f5.common import fq_name
127    from library.module_utils.network.f5.common import transform_name
128    from library.module_utils.network.f5.common import f5_argument_spec
129    from library.module_utils.network.f5.icontrol import upload_file
130    from library.module_utils.network.f5.icontrol import tmos_version
131    from library.module_utils.network.f5.icontrol import module_provisioned
132except ImportError:
133    from ansible.module_utils.network.f5.bigip import F5RestClient
134    from ansible.module_utils.network.f5.common import F5ModuleError
135    from ansible.module_utils.network.f5.common import AnsibleF5Parameters
136    from ansible.module_utils.network.f5.common import fq_name
137    from ansible.module_utils.network.f5.common import transform_name
138    from ansible.module_utils.network.f5.common import f5_argument_spec
139    from ansible.module_utils.network.f5.icontrol import upload_file
140    from ansible.module_utils.network.f5.icontrol import tmos_version
141    from ansible.module_utils.network.f5.icontrol import module_provisioned
142
143
144class Parameters(AnsibleF5Parameters):
145    api_map = {
146
147    }
148
149    api_attributes = [
150
151    ]
152
153    returnables = [
154        'name',
155        'source',
156        'type',
157
158    ]
159
160    updatables = [
161
162    ]
163
164
165class ApiParameters(Parameters):
166    pass
167
168
169class ModuleParameters(Parameters):
170    pass
171
172
173class Changes(Parameters):
174    def to_return(self):
175        result = {}
176        try:
177            for returnable in self.returnables:
178                result[returnable] = getattr(self, returnable)
179            result = self._filter_params(result)
180        except Exception:
181            pass
182        return result
183
184
185class UsableChanges(Changes):
186    pass
187
188
189class ReportableChanges(Changes):
190    pass
191
192
193class Difference(object):
194    def __init__(self, want, have=None):
195        self.want = want
196        self.have = have
197
198    def compare(self, param):
199        try:
200            result = getattr(self, param)
201            return result
202        except AttributeError:
203            return self.__default(param)
204
205    def __default(self, param):
206        attr1 = getattr(self.want, param)
207        try:
208            attr2 = getattr(self.have, param)
209            if attr1 != attr2:
210                return attr1
211        except AttributeError:
212            return attr1
213
214
215class ModuleManager(object):
216    def __init__(self, *args, **kwargs):
217        self.module = kwargs.get('module', None)
218        self.client = F5RestClient(**self.module.params)
219        self.want = ModuleParameters(params=self.module.params)
220        self.changes = UsableChanges()
221
222    def _set_changed_options(self):
223        changed = {}
224        for key in Parameters.returnables:
225            if getattr(self.want, key) is not None:
226                changed[key] = getattr(self.want, key)
227        if changed:
228            self.changes = UsableChanges(params=changed)
229
230    def _announce_deprecations(self, result):
231        warnings = result.pop('__warnings', [])
232        for warning in warnings:
233            self.client.module.deprecate(
234                msg=warning['msg'],
235                version=warning['version']
236            )
237
238    def exec_module(self):
239        if not module_provisioned(self.client, 'apm'):
240            raise F5ModuleError(
241                "APM must be provisioned to use this module."
242            )
243
244        if self.version_less_than_14():
245            raise F5ModuleError('Due to bug ID685681 it is not possible to use this module on TMOS version below 14.x')
246
247        result = dict()
248
249        changed = self.policy_import()
250
251        reportable = ReportableChanges(params=self.changes.to_return())
252        changes = reportable.to_return()
253        result.update(**changes)
254        result.update(dict(changed=changed))
255        self._announce_deprecations(result)
256        return result
257
258    def version_less_than_14(self):
259        version = tmos_version(self.client)
260        if LooseVersion(version) < LooseVersion('14.0.0'):
261            return True
262        return False
263
264    def policy_import(self):
265        self._set_changed_options()
266        if self.module.check_mode:
267            return True
268        if self.exists():
269            if self.want.force is False:
270                return False
271
272        self.import_file_to_device()
273        self.remove_temp_file_from_device()
274        return True
275
276    def exists(self):
277        if self.want.type == 'access_policy':
278            uri = "https://{0}:{1}/mgmt/tm/apm/policy/access-policy/{2}".format(
279                self.client.provider['server'],
280                self.client.provider['server_port'],
281                transform_name(self.want.partition, self.want.name)
282            )
283        else:
284            uri = "https://{0}:{1}/mgmt/tm/apm/profile/access/{2}".format(
285                self.client.provider['server'],
286                self.client.provider['server_port'],
287                transform_name(self.want.partition, self.want.name)
288            )
289        resp = self.client.api.get(uri)
290        try:
291            response = resp.json()
292        except ValueError:
293            return False
294        if resp.status == 404 or 'code' in response and response['code'] == 404:
295            return False
296        return True
297
298    def upload_file_to_device(self, content, name):
299        url = 'https://{0}:{1}/mgmt/shared/file-transfer/uploads'.format(
300            self.client.provider['server'],
301            self.client.provider['server_port']
302        )
303        try:
304            upload_file(self.client, url, content, name)
305        except F5ModuleError:
306            raise F5ModuleError(
307                "Failed to upload the file."
308            )
309
310    def import_file_to_device(self):
311        name = os.path.split(self.want.source)[1]
312        self.upload_file_to_device(self.want.source, name)
313
314        cmd = 'ng_import -s /var/config/rest/downloads/{0} {1} -p {2}'.format(name, self.want.name, self.want.partition)
315
316        uri = "https://{0}:{1}/mgmt/tm/util/bash/".format(
317            self.client.provider['server'],
318            self.client.provider['server_port'],
319        )
320        args = dict(
321            command='run',
322            utilCmdArgs='-c "{0}"'.format(cmd)
323        )
324        resp = self.client.api.post(uri, json=args)
325
326        try:
327            response = resp.json()
328            if 'commandResult' in response:
329                raise F5ModuleError(response['commandResult'])
330        except ValueError as ex:
331            raise F5ModuleError(str(ex))
332
333        if 'code' in response and response['code'] == 400:
334            if 'message' in response:
335                raise F5ModuleError(response['message'])
336            else:
337                raise F5ModuleError(resp.content)
338        return True
339
340    def remove_temp_file_from_device(self):
341        name = os.path.split(self.want.source)[1]
342        tpath_name = '/var/config/rest/downloads/{0}'.format(name)
343        uri = "https://{0}:{1}/mgmt/tm/util/unix-rm/".format(
344            self.client.provider['server'],
345            self.client.provider['server_port'],
346        )
347        args = dict(
348            command='run',
349            utilCmdArgs=tpath_name
350        )
351        resp = self.client.api.post(uri, json=args)
352        try:
353            response = resp.json()
354        except ValueError as ex:
355            raise F5ModuleError(str(ex))
356        if 'code' in response and response['code'] == 400:
357            if 'message' in response:
358                raise F5ModuleError(response['message'])
359            else:
360                raise F5ModuleError(resp.content)
361
362
363class ArgumentSpec(object):
364    def __init__(self):
365        self.supports_check_mode = True
366        argument_spec = dict(
367            name=dict(
368                required=True,
369            ),
370            source=dict(type='path'),
371            force=dict(
372                type='bool',
373                default='no'
374            ),
375            type=dict(
376                default='profile_access',
377                choices=['profile_access', 'access_policy']
378            ),
379            partition=dict(
380                default='Common',
381                fallback=(env_fallback, ['F5_PARTITION'])
382            )
383        )
384        self.argument_spec = {}
385        self.argument_spec.update(f5_argument_spec)
386        self.argument_spec.update(argument_spec)
387
388
389def main():
390    spec = ArgumentSpec()
391
392    module = AnsibleModule(
393        argument_spec=spec.argument_spec,
394        supports_check_mode=spec.supports_check_mode,
395    )
396
397    try:
398        mm = ModuleManager(module=module)
399        results = mm.exec_module()
400        module.exit_json(**results)
401    except F5ModuleError as ex:
402        module.fail_json(msg=str(ex))
403
404
405if __name__ == '__main__':
406    main()
407