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