Skip to content

Commit 363ad89

Browse files
authored
Add subnet filters for the DF service and Datahub clusters (#64)
* Fix ignored loadbalancer_subnets parameter due to being extracted with the wrong name (lb_subnets) * Add cluster_subnets_filter and loadbalancer_subnets_filter options * Fix inaccuracies and missing parts in the module docs Signed-off-by: Andre Araujo <araujo@cloudera.com>
1 parent 505f3aa commit 363ad89

File tree

2 files changed

+197
-35
lines changed

2 files changed

+197
-35
lines changed

plugins/modules/datahub_cluster.py

Lines changed: 89 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
# See the License for the specific language governing permissions and
1616
# limitations under the License.
1717

18+
import jmespath
1819
from ansible.module_utils.basic import AnsibleModule
1920
from ansible_collections.cloudera.cloud.plugins.module_utils.cdp_common import CdpModule
2021

@@ -75,13 +76,30 @@
7576
type: str
7677
required: False
7778
subnet:
78-
description: The subnet ID in AWS, or the Subnet Name on Azure or GCP
79+
description:
80+
- The subnet ID in AWS, or the Subnet Name on Azure or GCP
81+
- Mutually exclusive with the subnet and subnets options
7982
type: str
8083
required: False
8184
samples:
8285
- Azure: fe-az-f0-sbnt-2
8386
- AWS: subnet-0bb1c79de3EXAMPLE
8487
- GCP: fe-gc-j8-sbnt-0
88+
subnets:
89+
description:
90+
- List of subnet IDs in case of multi availability zone setup.
91+
- Mutually exclusive with the subnet and subnets options
92+
type: list
93+
required: False
94+
subnets_filter:
95+
description:
96+
- L(JMESPath,https://jmespath.org/) expression to filter the subnets to be used for the load balancer
97+
- The expression will be applied to the full list of subnets for the specified environment
98+
- Each subnet in the list is an object with the following attributes: subnetId, subnetName, availabilityZone, cidr
99+
- The filter expression must only filter the list, but not apply any attribute projection
100+
- Mutually exclusive with the subnet and subnets options
101+
type: list
102+
required: False
85103
image:
86104
description: ID of the image used for cluster instances
87105
type: str
@@ -152,6 +170,11 @@
152170
required: False
153171
aliases:
154172
- datahub_tags
173+
extension:
174+
description:
175+
- Cluster extensions for Data Hub cluster.
176+
type: str
177+
required: False
155178
force:
156179
description:
157180
- Flag indicating if the datahub should be force deleted.
@@ -394,11 +417,15 @@ def __init__(self, module):
394417
self.environment = self._get_param('environment')
395418
self.definition = self._get_param('definition')
396419
self.subnet = self._get_param('subnet')
420+
self.subnets = self._get_param('subnets')
421+
self.subnets_filter = self._get_param('subnets_filter')
397422
self.image_id = self._get_param('image')
398423
self.image_catalog = self._get_param('catalog')
399424
self.template = self._get_param('template')
400425
self.groups = self._get_param('groups')
401426
self.tags = self._get_param('tags')
427+
self.extension = self._get_param('extension')
428+
self.multi_az = self._get_param('multi_az')
402429

403430
self.wait = self._get_param('wait')
404431
self.delay = self._get_param('delay')
@@ -542,16 +569,40 @@ def _configure_payload(self):
542569
)
543570

544571
if self.definition is not None:
545-
payload["clusterDefinitionName"]=self.definition
572+
payload["clusterDefinitionName"] = self.definition
546573
else:
547-
payload["image"]={"id": self.image_id, "catalogName": self.image_catalog}
548-
payload["clusterTemplateName"]=self.template
549-
payload["instanceGroups"]=self.groups
574+
payload["image"] = {"id": self.image_id, "catalogName": self.image_catalog}
575+
payload["clusterTemplateName"] = self.template
576+
payload["instanceGroups"] = self.groups
577+
578+
if self.subnets_filter:
579+
try:
580+
env_info = self.cdpy.environments.describe_environment(self.environment)
581+
subnet_metadata = list(env_info['network']['subnetMetadata'].values())
582+
except Exception:
583+
subnet_metadata = []
584+
if not subnet_metadata:
585+
self.module.fail_json(
586+
msg="Could not retrieve subnet metadata for CDP Environment %s" % self.env_crn)
587+
588+
subnets = self._filter_subnets(self.subnets_filter, subnet_metadata)
589+
if len(subnets) == 1:
590+
self.subnet = subnets[0]
591+
else:
592+
self.subnets = subnets
550593

551-
if self.host_env['cloudPlatform'] == 'GCP':
552-
payload['subnetName'] = self.subnet
553-
else:
554-
payload['subnetId'] = self.subnet
594+
if self.subnet:
595+
if self.host_env['cloudPlatform'] == 'GCP':
596+
payload['subnetName'] = self.subnet
597+
else:
598+
payload['subnetId'] = self.subnet
599+
elif self.subnets:
600+
payload['subnetIds'] = self.subnets
601+
602+
if self.extension is not None:
603+
payload['clusterExtension'] = self.extension
604+
605+
payload['multiAz'] = self.multi_az
555606

556607
if self.tags is not None:
557608
payload['tags'] = list()
@@ -560,6 +611,27 @@ def _configure_payload(self):
560611

561612
return payload
562613

614+
def _filter_subnets(self, query, subnets):
615+
"""Apply a JMESPath to an array of subnets and return the id of the selected subnets.
616+
The query must only filter the array, without applying any projection. The query result must also be an
617+
array of subnet objects.
618+
619+
:param query: JMESpath query to filter the subnet array.
620+
:param subnets: An array of subnet objects. Each subnet in the array is an object with the following attributes:
621+
subnetId, subnetName, availabilityZone, cidr.
622+
:return: An array of subnet ids.
623+
"""
624+
filtered_subnets = []
625+
try:
626+
filtered_subnets = jmespath.search(query, subnets)
627+
except Exception:
628+
self.module.fail_json(msg="The specified subnet filter is an invalid JMESPath expression: " % query)
629+
try:
630+
return [s['subnetId'] for s in filtered_subnets]
631+
except Exception:
632+
self.module.fail_json(msg='The subnet filter "%s" should return an array of subnet objects '
633+
'but instead returned this: %s' % (query, json.dumps(filtered_subnets)))
634+
563635
def _reconcile_existing_state(self, existing):
564636
mismatched = list()
565637

@@ -594,19 +666,26 @@ def main():
594666
state=dict(required=False, type='str', choices=['present', 'started', 'stopped', 'absent'], default='present'),
595667
definition=dict(required=False, type='str'),
596668
subnet=dict(required=False, type='str', default=None),
669+
subnets=dict(required=False, type='list', elements='str', default=None),
670+
subnets_filter=dict(required=False, type='str', default=None),
597671
image=dict(required=False, type='str', default=None),
598672
catalog=dict(required=False, type='str', default=None),
599673
template=dict(required=False, type='str', default=None),
600674
groups=dict(required=False, type='list', default=None),
601675
environment=dict(required=False, type='str', aliases=['env'], default=None),
602676
tags=dict(required=False, type='dict', aliases=['datahub_tags']),
677+
extension=dict(required=False, type='dict'),
678+
multi_az=dict(required=False, type='bool', default=True),
603679

604680
force=dict(required=False, type='bool', default=False),
605681
wait=dict(required=False, type='bool', default=True),
606682
delay=dict(required=False, type='int', aliases=['polling_delay'], default=15),
607683
timeout=dict(required=False, type='int', aliases=['polling_timeout'], default=3600)
608684
),
609-
supports_check_mode=True
685+
supports_check_mode=True,
686+
mutually_exclusive=[
687+
('subnet', 'subnets', 'subnets_filter'),
688+
],
610689
#Punting on additional checks here. There are a variety of supporting datahub invocations that can make this more complex
611690
#required_together=[
612691
# ['subnet', 'image', 'catalog', 'template', 'groups', 'environment'],

0 commit comments

Comments
 (0)