1
0
Fork 0
mirror of https://github.com/ansible-collections/community.general.git synced 2024-09-14 20:13:21 +02:00

Adds the bigiq_regkey_license module (#34543)

This module can be used to add license offerings to a pool. This
is the second part required to license a remote device from a
BIG-IQ (the first being to create a pool with bigiq_regkey_pool)
This commit is contained in:
Tim Rupp 2018-01-06 12:13:50 -08:00 committed by GitHub
parent 8c07ebe860
commit f4a2b0b103
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 657 additions and 0 deletions

View file

@ -0,0 +1,443 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# Copyright: (c) 2017, F5 Networks Inc.
# GNU General Public License v3.0 (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = r'''
---
module: bigiq_regkey_license
short_description: Manages licenses in a BIG-IQ registration key pool
description:
- Manages licenses in a BIG-IQ registration key pool.
version_added: "2.5"
options:
regkey_pool:
description:
- The registration key pool that you want to place the license in.
- You must be mindful to name your registration pools unique names. While
BIG-IQ does not require this, this module does. If you do not do this,
the behavior of the module is undefined and you may end up putting
licenses in the wrong registration key pool.
required: True
license_key:
description:
- The license key to put in the pool.
required: True
description:
description:
- Description of the license.
accept_eula:
description:
- A key that signifies that you accept the F5 EULA for this license.
- A copy of the EULA can be found here https://askf5.f5.com/csp/article/K12902
- This is required when C(state) is C(present).
state:
description:
- The state of the regkey license in the pool on the system.
- When C(present), guarantees that the license exists in the pool.
- When C(absent), removes the license from the pool.
default: present
choices:
- absent
- present
requirements:
- BIG-IQ >= 5.3.0
extends_documentation_fragment: f5
author:
- Tim Rupp (@caphrim007)
'''
EXAMPLES = r'''
- name: Add a registration key license to a pool
bigiq_regkey_license:
regkey_pool: foo-pool
license_key: XXXXX-XXXXX-XXXXX-XXXXX-XXXXX
accept_eula: yes
password: secret
server: lb.mydomain.com
state: present
user: admin
delegate_to: localhost
- name: Remove a registration key license from a pool
bigiq_regkey_license:
regkey_pool: foo-pool
license_key: XXXXX-XXXXX-XXXXX-XXXXX-XXXXX
password: secret
server: lb.mydomain.com
state: absent
user: admin
delegate_to: localhost
'''
RETURN = r'''
description:
description: The new description of the license key.
returned: changed
type: string
sample: My license for BIG-IP 1
'''
import time
from ansible.module_utils.f5_utils import AnsibleF5Client
from ansible.module_utils.f5_utils import AnsibleF5Parameters
from ansible.module_utils.f5_utils import HAS_F5SDK
from ansible.module_utils.f5_utils import F5ModuleError
from ansible.module_utils.six import iteritems
from collections import defaultdict
try:
from ansible.module_utils.f5_utils import iControlUnexpectedHTTPError
except ImportError:
HAS_F5SDK = False
class Parameters(AnsibleF5Parameters):
api_map = {
'regKey': 'license_key'
}
api_attributes = [
'regKey', 'description'
]
returnables = [
'description'
]
updatables = [
'description'
]
def __init__(self, params=None):
self._values = defaultdict(lambda: None)
self._values['__warnings'] = []
if params:
self.update(params=params)
def update(self, params=None):
if params:
for k, v in iteritems(params):
if self.api_map is not None and k in self.api_map:
map_key = self.api_map[k]
else:
map_key = k
# Handle weird API parameters like `dns.proxy.__iter__` by
# using a map provided by the module developer
class_attr = getattr(type(self), map_key, None)
if isinstance(class_attr, property):
# There is a mapped value for the api_map key
if class_attr.fset is None:
# If the mapped value does not have
# an associated setter
self._values[map_key] = v
else:
# The mapped value has a setter
setattr(self, map_key, v)
else:
# If the mapped value is not a @property
self._values[map_key] = v
def to_return(self):
result = {}
try:
for returnable in self.returnables:
result[returnable] = getattr(self, returnable)
result = self._filter_params(result)
except Exception:
pass
return result
def api_params(self):
result = {}
for api_attribute in self.api_attributes:
if self.api_map is not None and api_attribute in self.api_map:
result[api_attribute] = getattr(self, self.api_map[api_attribute])
else:
result[api_attribute] = getattr(self, api_attribute)
result = self._filter_params(result)
return result
class ApiParameters(Parameters):
pass
class ModuleParameters(Parameters):
@property
def regkey_pool_uuid(self):
if self._values['regkey_pool_uuid']:
return self._values['regkey_pool_uuid']
collection = self.client.api.cm.device.licensing.pool.regkey.licenses_s.get_collection()
resource = next((x for x in collection if x.name == self.regkey_pool), None)
if resource is None:
raise F5ModuleError("Could not find the specified regkey pool.")
self._values['regkey_pool_uuid'] = resource.id
return resource.id
class Changes(Parameters):
def to_return(self):
result = {}
try:
for returnable in self.returnables:
result[returnable] = getattr(self, returnable)
result = self._filter_params(result)
except Exception:
pass
return result
class UsableChanges(Changes):
pass
class ReportableChanges(Changes):
pass
class Difference(object):
def __init__(self, want, have=None):
self.want = want
self.have = have
def compare(self, param):
try:
result = getattr(self, param)
return result
except AttributeError:
return self.__default(param)
def __default(self, param):
attr1 = getattr(self.want, param)
try:
attr2 = getattr(self.have, param)
if attr1 != attr2:
return attr1
except AttributeError:
return attr1
class ModuleManager(object):
def __init__(self, client):
self.client = client
self.want = ModuleParameters(params=self.client.module.params)
self.want.update(dict(client=client))
self.have = ApiParameters()
self.changes = UsableChanges()
def _set_changed_options(self):
changed = {}
for key in Parameters.returnables:
if getattr(self.want, key) is not None:
changed[key] = getattr(self.want, key)
if changed:
self.changes = UsableChanges(changed)
def _update_changed_options(self):
diff = Difference(self.want, self.have)
updatables = Parameters.updatables
changed = dict()
for k in updatables:
change = diff.compare(k)
if change is None:
continue
else:
if isinstance(change, dict):
changed.update(change)
else:
changed[k] = change
if changed:
self.changes = UsableChanges(changed)
return True
return False
def should_update(self):
result = self._update_changed_options()
if result:
return True
return False
def exec_module(self):
changed = False
result = dict()
state = self.want.state
try:
if state == "present":
changed = self.present()
elif state == "absent":
changed = self.absent()
except iControlUnexpectedHTTPError as e:
raise F5ModuleError(str(e))
reportable = ReportableChanges(self.changes.to_return())
changes = reportable.to_return()
result.update(**changes)
result.update(dict(changed=changed))
self._announce_deprecations(result)
return result
def _announce_deprecations(self, result):
warnings = result.pop('__warnings', [])
for warning in warnings:
self.client.module.deprecate(
msg=warning['msg'],
version=warning['version']
)
def present(self):
if self.exists():
return self.update()
else:
return self.create()
def exists(self):
collection = self.client.api.cm.device.licensing.pool.regkey.licenses_s
pool = collection.licenses.load(id=self.want.regkey_pool_uuid)
collection = pool.offerings_s.get_collection()
resource = next((x for x in collection if x.regKey == self.want.license_key), None)
if resource is None:
return False
return True
def update(self):
self.have = self.read_current_from_device()
if not self.should_update():
return False
if self.client.check_mode:
return True
self.update_on_device()
return True
def remove(self):
if self.client.check_mode:
return True
self.remove_from_device()
if self.exists():
raise F5ModuleError("Failed to delete the resource.")
return True
def create(self):
self._set_changed_options()
if self.client.check_mode:
return True
if self.want.accept_eula is False:
raise F5ModuleError(
"To add a license, you must accept its EULA. Please see the module documentation for a link to this."
)
self.create_on_device()
return True
def create_on_device(self):
params = self.want.api_params()
collection = self.client.api.cm.device.licensing.pool.regkey.licenses_s
pool = collection.licenses.load(id=self.want.regkey_pool_uuid)
resource = pool.offerings_s.offerings.create(
status='ACTIVATING_AUTOMATIC',
**params
)
for x in range(60):
resource.refresh()
if resource.status == 'READY':
break
elif resource.status == 'ACTIVATING_AUTOMATIC_NEED_EULA_ACCEPT':
resource.modify(
status='ACTIVATING_AUTOMATIC_EULA_ACCEPTED',
eulaText=resource.eulaText
)
elif resource.status == 'ACTIVATION_FAILED':
raise F5ModuleError(str(resource.message))
time.sleep(1)
def wait_for_status(self, resource, status):
for x in range(60):
resource.refresh()
if resource.status == status:
return
time.sleep(1)
def update_on_device(self):
params = self.changes.api_params()
collection = self.client.api.cm.device.licensing.pool.regkey.licenses_s
pool = collection.licenses.load(id=self.want.regkey_pool_uuid)
collection = pool.offerings_s.get_collection()
resource = next((x for x in collection if x.regKey == self.want.license_key), None)
if resource is None:
return False
resource.modify(**params)
def absent(self):
if self.exists():
return self.remove()
return False
def remove_from_device(self):
collection = self.client.api.cm.device.licensing.pool.regkey.licenses_s
pool = collection.licenses.load(id=self.want.regkey_pool_uuid)
collection = pool.offerings_s.get_collection()
resource = next((x for x in collection if x.regKey == self.want.license_key), None)
if resource is None:
return False
if resource:
resource.delete()
def read_current_from_device(self):
collection = self.client.api.cm.device.licensing.pool.regkey.licenses_s
pool = collection.licenses.load(id=self.want.regkey_pool_uuid)
collection = pool.offerings_s.get_collection()
resource = next((x for x in collection if x.regKey == self.want.license_key), None)
if resource is None:
return False
result = resource.attrs
return ApiParameters(result)
class ArgumentSpec(object):
def __init__(self):
self.supports_check_mode = True
self.argument_spec = dict(
regkey_pool=dict(required=True),
license_key=dict(required=True, no_log=True),
description=dict(),
accept_eula=dict(type='bool')
)
self.f5_product_name = 'bigiq'
self.required_if = [
['state', 'present', ['accept_eula']]
]
def main():
if not HAS_F5SDK:
raise F5ModuleError("The python f5-sdk module is required")
spec = ArgumentSpec()
client = AnsibleF5Client(
argument_spec=spec.argument_spec,
supports_check_mode=spec.supports_check_mode,
f5_product_name=spec.f5_product_name
)
try:
mm = ModuleManager(client)
results = mm.exec_module()
client.module.exit_json(**results)
except F5ModuleError as e:
client.module.fail_json(msg=str(e))
if __name__ == '__main__':
main()

View file

@ -0,0 +1,96 @@
{
"description": "foo bar baz",
"dossier": "5d54d976",
"encryptedPrivateKey": [
27,
-10,
12,
58,
75
],
"generation": 2,
"internalPrivateKey": "sKpXEU7",
"kind": "cm:device:licensing:pool:regkey:licenses:item:offerings:regkeypoollicenseofferingstate",
"lastUpdateMicros": 1513107721123304,
"licenseState": {
"vendor": "F5 Networks, Inc.",
"licensedDateTime": "2017-12-12T00:00:00-08:00",
"licensedVersion": "5.3.0",
"evaluationStartDateTime": "2017-12-11T00:00:00-08:00",
"evaluationEndDateTime": "2018-01-12T00:00:00-08:00",
"licenseEndDateTime": "2018-01-12T00:00:00-08:00",
"licenseStartDateTime": "2017-12-11T00:00:00-08:00",
"registrationKey": "J7217-42420-80195-30148-6427094",
"dossier": "1a44262799bc",
"authorization": "03fc41d1e8666",
"usage": "F5 Internal Product Development",
"platformId": "Z100",
"authVers": "5b",
"serviceCheckDateTime": "2017-12-12T00:00:00-08:00",
"serviceStatus": "As of 2017-12-12 there is no active service contract. This may inhibit your ability to upgrade your software.",
"exclusivePlatform": [
"Z100",
"Z100A",
"Z100AzureCloud",
"Z100GoogleCloud",
"Z100K",
"Z100x",
"Z100H"
],
"activeModules": [
"APM, Max, VE (2500 CCU, 10000 Access Sessions)|P961057-1761515|Anti-Virus Checks",
"LTM, 10 Gbps, VE|T487107-2453693|, VE|DNSSEC",
"PEM, VE|X895364-1851682"
],
"optionalModules": [
"APM, Base, VE (50 CCU / 200 AS)",
"App Mode (TMSH Only, No Root/Bash)",
"Concurrent Users",
"Concurrent Users and Access Sessions, VE",
"FIPS 140-2 Level 1, BIG-IP VE-1G to 10G",
"IP Intelligence, 1Yr, VE",
"IP Intelligence, 1Yr, VE-10G",
"IP Intelligence, 3Yr, VE-10G",
"LTM to Better Bundle Upgrade, 10Gbps",
"PEM URL Filtering, 1Yr, HIGH PERF",
"PEM URL Filtering, 3Yr, HIGH PERF",
"Routing Bundle",
"Secure Web Gateway, 1Yr, VE",
"URL Filtering, 1Yr, VE"
],
"moduleEvaluations": [
{
"moduleName": "IP Intelligence, 3Yr, VE|SUBSCRIPTION",
"endDate": "soon"
},
{
"moduleName": "Secure Web Gateway, 3Yr, VE|SUBSCRIPTION",
"endDate": "soon"
},
{
"moduleName": "URL Filtering, 3Yr, VE|SUBSCRIPTION",
"endDate": "soon"
}
],
"featureFlags": [
{
"featureName": "gtm_rate_limit",
"featureValue": "12345"
}
],
"generation": 0,
"lastUpdateMicros": 0
},
"licenseText": "#Auth vers : BIG-IQ Product License File#",
"message": "License XXXX-XXXX-XXXX-XXXX-XXXX ready",
"name": "License for XXXX-XXXX-XXXX-XXXX-XXXX",
"publicKey": [
48,
0,
1
],
"regKey": "XXXX-XXXX-XXXX-XXXX-XXXX",
"selfLink": "https://localhost/mgmt/cm/device/licensing/pool/regkey/licenses/452f8628-1e56-4b4d-946c-0e68f5780aa1/offerings/XXXX-XXXX-XXXX-XXXX-XXXX",
"sortName": "Registration Key Pool Item",
"status": "READY"
}

View file

@ -0,0 +1,118 @@
# -*- coding: utf-8 -*-
#
# Copyright: (c) 2017, F5 Networks Inc.
# GNU General Public License v3.0 (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
import os
import json
import pytest
import sys
from nose.plugins.skip import SkipTest
if sys.version_info < (2, 7):
raise SkipTest("F5 Ansible modules require Python >= 2.7")
from ansible.compat.tests import unittest
from ansible.compat.tests.mock import Mock
from ansible.compat.tests.mock import patch
from ansible.module_utils.f5_utils import AnsibleF5Client
from ansible.module_utils.f5_utils import F5ModuleError
try:
from library.bigiq_regkey_license import ModuleParameters
from library.bigiq_regkey_license import ApiParameters
from library.bigiq_regkey_license import ModuleManager
from library.bigiq_regkey_license import ArgumentSpec
from ansible.module_utils.f5_utils import iControlUnexpectedHTTPError
from test.unit.modules.utils import set_module_args
except ImportError:
try:
from ansible.modules.network.f5.bigiq_regkey_license import ModuleParameters
from ansible.modules.network.f5.bigiq_regkey_license import ApiParameters
from ansible.modules.network.f5.bigiq_regkey_license import ModuleManager
from ansible.modules.network.f5.bigiq_regkey_license import ArgumentSpec
from ansible.module_utils.f5_utils import iControlUnexpectedHTTPError
from units.modules.utils import set_module_args
except ImportError:
raise SkipTest("F5 Ansible modules require the f5-sdk Python library")
fixture_path = os.path.join(os.path.dirname(__file__), 'fixtures')
fixture_data = {}
def load_fixture(name):
path = os.path.join(fixture_path, name)
if path in fixture_data:
return fixture_data[path]
with open(path) as f:
data = f.read()
try:
data = json.loads(data)
except Exception:
pass
fixture_data[path] = data
return data
class TestParameters(unittest.TestCase):
def test_module_parameters(self):
args = dict(
regkey_pool='foo',
license_key='XXXX-XXXX-XXXX-XXXX-XXXX',
accept_eula=True,
description='this is a description'
)
p = ModuleParameters(args)
assert p.regkey_pool == 'foo'
assert p.license_key == 'XXXX-XXXX-XXXX-XXXX-XXXX'
assert p.accept_eula is True
assert p.description == 'this is a description'
def test_api_parameters(self):
args = load_fixture('load_regkey_license_key.json')
p = ApiParameters(args)
assert p.description == 'foo bar baz'
@patch('ansible.module_utils.f5_utils.AnsibleF5Client._get_mgmt_root',
return_value=True)
class TestManager(unittest.TestCase):
def setUp(self):
self.spec = ArgumentSpec()
def test_create(self, *args):
set_module_args(dict(
regkey_pool='foo',
license_key='XXXX-XXXX-XXXX-XXXX-XXXX',
accept_eula=True,
description='this is a description',
password='passsword',
server='localhost',
user='admin'
))
client = AnsibleF5Client(
argument_spec=self.spec.argument_spec,
supports_check_mode=self.spec.supports_check_mode,
f5_product_name=self.spec.f5_product_name
)
mm = ModuleManager(client)
# Override methods to force specific logic in the module to happen
mm.exists = Mock(side_effect=[False, True])
mm.create_on_device = Mock(return_value=True)
results = mm.exec_module()
assert results['changed'] is True
assert results['description'] == 'this is a description'