From f3041033a09c5626a8e8463486ea06134c3b97a3 Mon Sep 17 00:00:00 2001 From: rsuplina Date: Mon, 24 Jun 2024 11:52:47 +0100 Subject: [PATCH 1/9] Add Host Template Module Signed-off-by: rsuplina --- plugins/modules/host_template.py | 181 ++++++++++++++++++ plugins/modules/host_template_info.py | 160 ++++++++++++++++ .../host_template/test_host_template.py | 82 ++++++++ .../test_host_template_info.py | 80 ++++++++ 4 files changed, 503 insertions(+) create mode 100644 plugins/modules/host_template.py create mode 100644 plugins/modules/host_template_info.py create mode 100644 tests/unit/plugins/modules/host_template/test_host_template.py create mode 100644 tests/unit/plugins/modules/host_template_info/test_host_template_info.py diff --git a/plugins/modules/host_template.py b/plugins/modules/host_template.py new file mode 100644 index 00000000..d6d61909 --- /dev/null +++ b/plugins/modules/host_template.py @@ -0,0 +1,181 @@ +# Copyright 2024 Cloudera, Inc. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from ansible_collections.cloudera.cluster.plugins.module_utils.cm_utils import ( + ClouderaManagerModule, +) +from cm_client import HostTemplatesResourceApi,ClustersResourceApi,ApiHostTemplate,ApiRoleConfigGroupRef,ApiClusterRef,ApiHostTemplateList +from cm_client.rest import ApiException + +ANSIBLE_METADATA = { + "metadata_version": "1.1", + "status": ["preview"], + "supported_by": "community", +} + +DOCUMENTATION = r""" +--- +module: host_template +short_description: Configure a host template +description: + - Creates a new host template or updates an existing one + - The module supports C(check_mode). +author: + - "Ronald Suplina (@rsuplina)" +requirements: + - cm_client +options: + name: + description: + - The associated cluster name. + type: str + required: yes + aliases: + - cluster_name + host_template_name: + description: + - The name of the host template. + type: str + required: yes + roleConfigGroupRefs: + description: + - The names of the role config groups + type: list + returned: yes +""" + +EXAMPLES = r""" +--- +- name: Create host template + cloudera.cluster.host_template + host: example.cloudera.com + username: "jane_smith" + password: "S&peR4Ec*re" + name: "base_cluster" + role_configs_groups: ["kafka-GATEWAY-BASE", "atlas-ATLAS_SERVER-BASE" , "hive_on_tez-GATEWAY-BASE"] + +- name: Update host template + cloudera.cluster.host_template + host: example.cloudera.com + username: "jane_smith" + password: "S&peR4Ec*re" + name: "base_cluster" + role_configs_groups: ["kafka-GATEWAY-BASE", "atlas-ATLAS_SERVER-BASE"] +""" + +RETURN = r""" +--- +cloudera_manager: + description: + - Retrieve details about host template. + type: dict + elements: dict + returned: always + contains: + name: + description: + - The name of the host template + type: str + returned: always + clusterRef: + description: A reference to the enclosing cluster. + type: dict + returned: always + roleConfigGroupRefs: + description: + - The role config groups belonging to this host tempalte. + type: list + returned: always +""" + + +class ClouderaHostTemplate(ClouderaManagerModule): + def __init__(self, module): + super(ClouderaHostTemplate, self).__init__(module) + + # Set the parameters + self.cluster_name = self.get_param("name") + self.host_template_name = self.get_param("host_template_name") + self.role_configs_groups = self.get_param("role_configs_groups") + + # Initialize the return value + self.host_template = [] + self.host_template_output = [] + + # Execute the logic + self.process() + + @ClouderaManagerModule.handle_process + def process(self): + host_temp_api_instance = HostTemplatesResourceApi(self.api_client) + + try: + ClustersResourceApi(self.api_client).read_cluster(self.cluster_name) + except ApiException as ex: + if ex.status == 404: + self.module.fail_json(msg="Cluster does not exist: " + self.cluster_name) + else: + raise ex + + if not self.module.check_mode: + try: + self.host_template = host_temp_api_instance.read_host_template(cluster_name=self.cluster_name,host_template_name=self.host_template_name) + except ApiException as ex: + if ex.status == 404: + pass + else: + raise ex + + host_template = ApiHostTemplate( + cluster_ref=ApiClusterRef(cluster_name=self.cluster_name,display_name=self.cluster_name), + name=self.host_template_name, + role_config_group_refs=[ApiRoleConfigGroupRef(role_config_group_name=group) for group in self.role_configs_groups] + ) + if self.host_template: + self.host_template_output = host_temp_api_instance.update_host_template(cluster_name=self.cluster_name,host_template_name=self.host_template_name,body=host_template) + self.changed = True + else: + body = ApiHostTemplateList(items=[host_template]) + self.host_template_output = host_temp_api_instance.create_host_templates(cluster_name=self.cluster_name,body=body) + self.changed = True + + self.host_template_output = host_temp_api_instance.read_host_template(cluster_name=self.cluster_name,host_template_name=self.host_template_name).to_dict() + + +def main(): + module = ClouderaManagerModule.ansible_module( + argument_spec=dict( + name=dict(required=True, type="str", aliases=["cluster_name"]), + host_template_name=dict(required=True, type="str"), + role_configs_groups=dict(required=True, type="list") + ), + supports_check_mode=True, + ) + + result = ClouderaHostTemplate(module) + + output = dict( + changed=result.changed, + host_template_output=result.host_template_output, + ) + + if result.debug: + log = result.log_capture.getvalue() + output.update(debug=log, debug_lines=log.split("\n")) + + module.exit_json(**output) + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/plugins/modules/host_template_info.py b/plugins/modules/host_template_info.py new file mode 100644 index 00000000..521ea292 --- /dev/null +++ b/plugins/modules/host_template_info.py @@ -0,0 +1,160 @@ +# Copyright 2024 Cloudera, Inc. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from ansible_collections.cloudera.cluster.plugins.module_utils.cm_utils import ( + ClouderaManagerModule, +) +from cm_client import HostTemplatesResourceApi, ClustersResourceApi +from cm_client.rest import ApiException + +ANSIBLE_METADATA = { + "metadata_version": "1.1", + "status": ["preview"], + "supported_by": "community", +} + +DOCUMENTATION = r""" +--- +module: host_template_info +short_description: Retrieve details of host templates. +description: + - Collects detailed information about individual or all host templates. + - The module supports C(check_mode). +author: + - "Ronald Suplina (@rsuplina)" +requirements: + - cm_client +options: + name: + description: + - The associated cluster name. + type: str + required: yes + aliases: + - cluster_name + host_template_name: + description: + - The name of the host template. + type: str + required: no +""" + +EXAMPLES = r""" +--- +- name: Retrieve the defailts about a specific host template + cloudera.cluster.host_template_info + host: example.cloudera.com + username: "jane_smith" + password: "S&peR4Ec*re" + name: "cfm_cluster" + host_template_name: "cfm_host_template" + +- name: Retrieve the details about all host templates within the cluster + cloudera.cluster.host_template_info + host: example.cloudera.com + username: "jane_smith" + password: "S&peR4Ec*re" + name: "cfm_cluster" +""" + +RETURN = r""" +--- +cloudera_manager: + description: + - Details about host template. + type: list + elements: dict + returned: always + contains: + name: + description: + - The name of the host template + type: str + returned: always + clusterRef: + description: A reference to the enclosing cluster. + type: dict + returned: always + roleConfigGroupRefs: + description: + - The names of the role config groups + type: list + returned: always +""" + + +class ClouderaHostTemplateInfo(ClouderaManagerModule): + def __init__(self, module): + super(ClouderaHostTemplateInfo, self).__init__(module) + + # Set the parameters + self.cluster_name = self.get_param("name") + self.host_template_name = self.get_param("host_template_name") + + # Initialize the return value + self.host_templates_output = [] + + # Execute the logic + self.process() + + @ClouderaManagerModule.handle_process + def process(self): + try: + ClustersResourceApi(self.api_client).read_cluster(self.cluster_name) + except ApiException as ex: + if ex.status == 404: + self.module.fail_json(msg="Cluster does not exist: " + self.cluster_name) + else: + raise ex + + host_temp_api_instance = HostTemplatesResourceApi(self.api_client) + if self.host_template_name: + + try: + self.host_templates_output = host_temp_api_instance.read_host_template(cluster_name=self.cluster_name,host_template_name=self.host_template_name).to_dict() + except ApiException as ex: + if ex.status == 404: + self.module.fail_json(msg="Host Template does not exist: " + self.host_template_name) + else: + raise ex + + else: + self.host_templates_output = host_temp_api_instance.read_host_templates(cluster_name=self.cluster_name).items + + +def main(): + module = ClouderaManagerModule.ansible_module( + argument_spec=dict( + name=dict(required=True, type="str", aliases=["cluster_name"]), + host_template_name=dict(required=False, type="str") + ), + supports_check_mode=True, + ) + + result = ClouderaHostTemplateInfo(module) + + output = dict( + changed=False, + host_templates_output=result.host_templates_output, + ) + + if result.debug: + log = result.log_capture.getvalue() + output.update(debug=log, debug_lines=log.split("\n")) + + module.exit_json(**output) + + +if __name__ == "__main__": + main() diff --git a/tests/unit/plugins/modules/host_template/test_host_template.py b/tests/unit/plugins/modules/host_template/test_host_template.py new file mode 100644 index 00000000..c2136174 --- /dev/null +++ b/tests/unit/plugins/modules/host_template/test_host_template.py @@ -0,0 +1,82 @@ +# Copyright 2024 Cloudera, Inc. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +import logging +import os +import pytest + +from ansible_collections.cloudera.cluster.plugins.modules import host_template +from ansible_collections.cloudera.cluster.tests.unit import ( + AnsibleExitJson, + AnsibleFailJson, +) + +LOG = logging.getLogger(__name__) + + +@pytest.fixture +def conn(): + conn = dict(username=os.getenv("CM_USERNAME"), password=os.getenv("CM_PASSWORD")) + + if os.getenv("CM_HOST", None): + conn.update(host=os.getenv("CM_HOST")) + + if os.getenv("CM_PORT", None): + conn.update(port=os.getenv("CM_PORT")) + + if os.getenv("CM_ENDPOINT", None): + conn.update(url=os.getenv("CM_ENDPOINT")) + + if os.getenv("CM_PROXY", None): + conn.update(proxy=os.getenv("CM_PROXY")) + + return { + **conn, + "verify_tls": "no", + "debug": "no", + } + + +def test_create_host_template(module_args, conn): + conn.update( + name="TestCluster", + host_template_name="MyTemplate", + role_configs_groups = ['atlas-ATLAS_SERVER-BASE', 'atlas-GATEWAY-BASE'] + ) + + module_args(conn) + + with pytest.raises(AnsibleExitJson) as e: + host_template.main() + + LOG.info(str(e.value.host_template_output)) + +def test_update_host_template(module_args, conn): + conn.update( + name="TestCluster", + host_template_name="MyTemplate", + role_configs_groups = ['atlas-ATLAS_SERVER-BASE', 'atlas-GATEWAY-BASE','tez-GATEWAY-BASE','hdfs-NAMENODE-BASE'] + ) + + module_args(conn) + + with pytest.raises(AnsibleExitJson) as e: + host_template.main() + + LOG.info(str(e.value.host_template_output)) + diff --git a/tests/unit/plugins/modules/host_template_info/test_host_template_info.py b/tests/unit/plugins/modules/host_template_info/test_host_template_info.py new file mode 100644 index 00000000..0ee67bfc --- /dev/null +++ b/tests/unit/plugins/modules/host_template_info/test_host_template_info.py @@ -0,0 +1,80 @@ +# Copyright 2024 Cloudera, Inc. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +import logging +import os +import pytest + +from ansible_collections.cloudera.cluster.plugins.modules import host_template_info +from ansible_collections.cloudera.cluster.tests.unit import ( + AnsibleExitJson, + AnsibleFailJson, +) + +LOG = logging.getLogger(__name__) + + +@pytest.fixture +def conn(): + conn = dict(username=os.getenv("CM_USERNAME"), password=os.getenv("CM_PASSWORD")) + + if os.getenv("CM_HOST", None): + conn.update(host=os.getenv("CM_HOST")) + + if os.getenv("CM_PORT", None): + conn.update(port=os.getenv("CM_PORT")) + + if os.getenv("CM_ENDPOINT", None): + conn.update(url=os.getenv("CM_ENDPOINT")) + + if os.getenv("CM_PROXY", None): + conn.update(proxy=os.getenv("CM_PROXY")) + + return { + **conn, + "verify_tls": "no", + "debug": "no", + } + + +def test_all_host_templates(module_args, conn): + conn.update( + name="TestCluster", + ) + + module_args(conn) + + with pytest.raises(AnsibleExitJson) as e: + host_template_info.main() + + LOG.info(str(e.value.host_templates_output)) + + +def test_single_host_template(module_args, conn): + conn.update( + name="TestCluster", + host_template_name="template1", + ) + + module_args(conn) + + with pytest.raises(AnsibleExitJson) as e: + host_template_info.main() + + LOG.info(str(e.value.host_templates_output)) + From 68e732d1022de2369060be7cf2288353f399d3ee Mon Sep 17 00:00:00 2001 From: rsuplina Date: Mon, 24 Jun 2024 14:31:29 +0100 Subject: [PATCH 2/9] Add Linting Signed-off-by: rsuplina --- plugins/modules/host_template.py | 82 +++++++++++++------ plugins/modules/host_template_info.py | 34 +++++--- .../host_template/test_host_template.py | 11 ++- .../test_host_template_info.py | 1 - 4 files changed, 84 insertions(+), 44 deletions(-) diff --git a/plugins/modules/host_template.py b/plugins/modules/host_template.py index d6d61909..1f8aa211 100644 --- a/plugins/modules/host_template.py +++ b/plugins/modules/host_template.py @@ -15,7 +15,14 @@ from ansible_collections.cloudera.cluster.plugins.module_utils.cm_utils import ( ClouderaManagerModule, ) -from cm_client import HostTemplatesResourceApi,ClustersResourceApi,ApiHostTemplate,ApiRoleConfigGroupRef,ApiClusterRef,ApiHostTemplateList +from cm_client import ( + HostTemplatesResourceApi, + ClustersResourceApi, + ApiHostTemplate, + ApiRoleConfigGroupRef, + ApiClusterRef, + ApiHostTemplateList, +) from cm_client.rest import ApiException ANSIBLE_METADATA = { @@ -124,33 +131,54 @@ def process(self): ClustersResourceApi(self.api_client).read_cluster(self.cluster_name) except ApiException as ex: if ex.status == 404: - self.module.fail_json(msg="Cluster does not exist: " + self.cluster_name) + self.module.fail_json( + msg="Cluster does not exist: " + self.cluster_name + ) else: raise ex if not self.module.check_mode: - try: - self.host_template = host_temp_api_instance.read_host_template(cluster_name=self.cluster_name,host_template_name=self.host_template_name) - except ApiException as ex: - if ex.status == 404: - pass - else: - raise ex - - host_template = ApiHostTemplate( - cluster_ref=ApiClusterRef(cluster_name=self.cluster_name,display_name=self.cluster_name), - name=self.host_template_name, - role_config_group_refs=[ApiRoleConfigGroupRef(role_config_group_name=group) for group in self.role_configs_groups] - ) - if self.host_template: - self.host_template_output = host_temp_api_instance.update_host_template(cluster_name=self.cluster_name,host_template_name=self.host_template_name,body=host_template) - self.changed = True - else: - body = ApiHostTemplateList(items=[host_template]) - self.host_template_output = host_temp_api_instance.create_host_templates(cluster_name=self.cluster_name,body=body) - self.changed = True - - self.host_template_output = host_temp_api_instance.read_host_template(cluster_name=self.cluster_name,host_template_name=self.host_template_name).to_dict() + try: + self.host_template = host_temp_api_instance.read_host_template( + cluster_name=self.cluster_name, + host_template_name=self.host_template_name, + ) + except ApiException as ex: + if ex.status == 404: + pass + else: + raise ex + + host_template = ApiHostTemplate( + cluster_ref=ApiClusterRef( + cluster_name=self.cluster_name, display_name=self.cluster_name + ), + name=self.host_template_name, + role_config_group_refs=[ + ApiRoleConfigGroupRef(role_config_group_name=group) + for group in self.role_configs_groups + ], + ) + if self.host_template: + self.host_template_output = host_temp_api_instance.update_host_template( + cluster_name=self.cluster_name, + host_template_name=self.host_template_name, + body=host_template, + ) + self.changed = True + else: + body = ApiHostTemplateList(items=[host_template]) + self.host_template_output = ( + host_temp_api_instance.create_host_templates( + cluster_name=self.cluster_name, body=body + ) + ) + self.changed = True + + self.host_template_output = host_temp_api_instance.read_host_template( + cluster_name=self.cluster_name, + host_template_name=self.host_template_name, + ).to_dict() def main(): @@ -158,8 +186,8 @@ def main(): argument_spec=dict( name=dict(required=True, type="str", aliases=["cluster_name"]), host_template_name=dict(required=True, type="str"), - role_configs_groups=dict(required=True, type="list") - ), + role_configs_groups=dict(required=True, type="list"), + ), supports_check_mode=True, ) @@ -178,4 +206,4 @@ def main(): if __name__ == "__main__": - main() \ No newline at end of file + main() diff --git a/plugins/modules/host_template_info.py b/plugins/modules/host_template_info.py index 521ea292..3da3e129 100644 --- a/plugins/modules/host_template_info.py +++ b/plugins/modules/host_template_info.py @@ -114,31 +114,39 @@ def process(self): ClustersResourceApi(self.api_client).read_cluster(self.cluster_name) except ApiException as ex: if ex.status == 404: - self.module.fail_json(msg="Cluster does not exist: " + self.cluster_name) + self.module.fail_json( + msg="Cluster does not exist: " + self.cluster_name + ) else: raise ex - + host_temp_api_instance = HostTemplatesResourceApi(self.api_client) if self.host_template_name: + try: + self.host_templates_output = host_temp_api_instance.read_host_template( + cluster_name=self.cluster_name, + host_template_name=self.host_template_name, + ).to_dict() + except ApiException as ex: + if ex.status == 404: + self.module.fail_json( + msg="Host Template does not exist: " + self.host_template_name + ) + else: + raise ex - try: - self.host_templates_output = host_temp_api_instance.read_host_template(cluster_name=self.cluster_name,host_template_name=self.host_template_name).to_dict() - except ApiException as ex: - if ex.status == 404: - self.module.fail_json(msg="Host Template does not exist: " + self.host_template_name) - else: - raise ex - else: - self.host_templates_output = host_temp_api_instance.read_host_templates(cluster_name=self.cluster_name).items + self.host_templates_output = host_temp_api_instance.read_host_templates( + cluster_name=self.cluster_name + ).items def main(): module = ClouderaManagerModule.ansible_module( argument_spec=dict( name=dict(required=True, type="str", aliases=["cluster_name"]), - host_template_name=dict(required=False, type="str") - ), + host_template_name=dict(required=False, type="str"), + ), supports_check_mode=True, ) diff --git a/tests/unit/plugins/modules/host_template/test_host_template.py b/tests/unit/plugins/modules/host_template/test_host_template.py index c2136174..ff2be452 100644 --- a/tests/unit/plugins/modules/host_template/test_host_template.py +++ b/tests/unit/plugins/modules/host_template/test_host_template.py @@ -56,7 +56,7 @@ def test_create_host_template(module_args, conn): conn.update( name="TestCluster", host_template_name="MyTemplate", - role_configs_groups = ['atlas-ATLAS_SERVER-BASE', 'atlas-GATEWAY-BASE'] + role_configs_groups=["atlas-ATLAS_SERVER-BASE", "atlas-GATEWAY-BASE"], ) module_args(conn) @@ -66,11 +66,17 @@ def test_create_host_template(module_args, conn): LOG.info(str(e.value.host_template_output)) + def test_update_host_template(module_args, conn): conn.update( name="TestCluster", host_template_name="MyTemplate", - role_configs_groups = ['atlas-ATLAS_SERVER-BASE', 'atlas-GATEWAY-BASE','tez-GATEWAY-BASE','hdfs-NAMENODE-BASE'] + role_configs_groups=[ + "atlas-ATLAS_SERVER-BASE", + "atlas-GATEWAY-BASE", + "tez-GATEWAY-BASE", + "hdfs-NAMENODE-BASE", + ], ) module_args(conn) @@ -79,4 +85,3 @@ def test_update_host_template(module_args, conn): host_template.main() LOG.info(str(e.value.host_template_output)) - diff --git a/tests/unit/plugins/modules/host_template_info/test_host_template_info.py b/tests/unit/plugins/modules/host_template_info/test_host_template_info.py index 0ee67bfc..5a2a9cdb 100644 --- a/tests/unit/plugins/modules/host_template_info/test_host_template_info.py +++ b/tests/unit/plugins/modules/host_template_info/test_host_template_info.py @@ -77,4 +77,3 @@ def test_single_host_template(module_args, conn): host_template_info.main() LOG.info(str(e.value.host_templates_output)) - From 381df12f1f22917094a4a583e5915af49666772c Mon Sep 17 00:00:00 2001 From: rsuplina Date: Mon, 24 Jun 2024 14:50:11 +0100 Subject: [PATCH 3/9] Add Lint Changes Signed-off-by: rsuplina --- plugins/modules/host_template.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/modules/host_template.py b/plugins/modules/host_template.py index 1f8aa211..e90bc8de 100644 --- a/plugins/modules/host_template.py +++ b/plugins/modules/host_template.py @@ -36,7 +36,7 @@ module: host_template short_description: Configure a host template description: - - Creates a new host template or updates an existing one + - Creates a new host template or updates an existing one - The module supports C(check_mode). author: - "Ronald Suplina (@rsuplina)" @@ -64,7 +64,7 @@ EXAMPLES = r""" --- -- name: Create host template +- name: Create host template cloudera.cluster.host_template host: example.cloudera.com username: "jane_smith" @@ -72,7 +72,7 @@ name: "base_cluster" role_configs_groups: ["kafka-GATEWAY-BASE", "atlas-ATLAS_SERVER-BASE" , "hive_on_tez-GATEWAY-BASE"] -- name: Update host template +- name: Update host template cloudera.cluster.host_template host: example.cloudera.com username: "jane_smith" From eaef9c389aafe6d55b002ba9b29edd4a7818c082 Mon Sep 17 00:00:00 2001 From: rsuplina Date: Thu, 11 Jul 2024 16:04:10 +0100 Subject: [PATCH 4/9] Add requested updates Signed-off-by: rsuplina --- plugins/modules/host_template.py | 149 ++++++++++++------ plugins/modules/host_template_info.py | 30 ++-- .../host_template/test_host_template.py | 33 +++- .../test_host_template_info.py | 6 +- 4 files changed, 144 insertions(+), 74 deletions(-) diff --git a/plugins/modules/host_template.py b/plugins/modules/host_template.py index e90bc8de..8f811397 100644 --- a/plugins/modules/host_template.py +++ b/plugins/modules/host_template.py @@ -15,6 +15,7 @@ from ansible_collections.cloudera.cluster.plugins.module_utils.cm_utils import ( ClouderaManagerModule, ) + from cm_client import ( HostTemplatesResourceApi, ClustersResourceApi, @@ -36,54 +37,72 @@ module: host_template short_description: Configure a host template description: - - Creates a new host template or updates an existing one + - Creates a new host template or updates an existing one - The module supports C(check_mode). author: - "Ronald Suplina (@rsuplina)" requirements: - cm_client options: - name: + cluster: description: - The associated cluster name. type: str required: yes aliases: - cluster_name - host_template_name: + name: description: - The name of the host template. type: str required: yes - roleConfigGroupRefs: + role_groups: description: - - The names of the role config groups + - Names of the role configuration groups associated with the host template. type: list returned: yes + aliases: + - role_config_groups +attributes: + check_mode: + support: full + diff_mode: + support: full """ EXAMPLES = r""" --- -- name: Create host template +- name: Create host template cloudera.cluster.host_template host: example.cloudera.com username: "jane_smith" password: "S&peR4Ec*re" - name: "base_cluster" - role_configs_groups: ["kafka-GATEWAY-BASE", "atlas-ATLAS_SERVER-BASE" , "hive_on_tez-GATEWAY-BASE"] + cluster: "base_cluster" + name: "MyTemplate" + role_groups: ["kafka-GATEWAY-BASE", "atlas-ATLAS_SERVER-BASE" , "hive_on_tez-GATEWAY-BASE"] -- name: Update host template +- name: Update host template cloudera.cluster.host_template host: example.cloudera.com username: "jane_smith" password: "S&peR4Ec*re" - name: "base_cluster" - role_configs_groups: ["kafka-GATEWAY-BASE", "atlas-ATLAS_SERVER-BASE"] + cluster: "base_cluster" + name: "MyTemplate" + role_groups: ["kafka-GATEWAY-BASE", "atlas-ATLAS_SERVER-BASE"] + +- name: Remove host template + cloudera.cluster.host_template + host: example.cloudera.com + username: "jane_smith" + password: "S&peR4Ec*re" + cluster: "base_cluster" + name: "MyTemplate" + state: "absent" """ RETURN = r""" --- -cloudera_manager: +host_template: description: - Retrieve details about host template. type: dict @@ -95,11 +114,11 @@ - The name of the host template type: str returned: always - clusterRef: + cluster_name: description: A reference to the enclosing cluster. - type: dict + type: str returned: always - roleConfigGroupRefs: + role_config_group_refs: description: - The role config groups belonging to this host tempalte. type: list @@ -112,13 +131,16 @@ def __init__(self, module): super(ClouderaHostTemplate, self).__init__(module) # Set the parameters - self.cluster_name = self.get_param("name") - self.host_template_name = self.get_param("host_template_name") - self.role_configs_groups = self.get_param("role_configs_groups") + self.cluster_name = self.get_param("cluster") + self.name = self.get_param("name") + self.role_groups = self.get_param("role_groups") + self.state = self.get_param("state") # Initialize the return value self.host_template = [] self.host_template_output = [] + self.changed = False + self.diff = {} # Execute the logic self.process() @@ -126,7 +148,6 @@ def __init__(self, module): @ClouderaManagerModule.handle_process def process(self): host_temp_api_instance = HostTemplatesResourceApi(self.api_client) - try: ClustersResourceApi(self.api_client).read_cluster(self.cluster_name) except ApiException as ex: @@ -136,57 +157,85 @@ def process(self): ) else: raise ex + try: + self.host_template = host_temp_api_instance.read_host_template( + cluster_name=self.cluster_name, + host_template_name=self.name, + ) + except ApiException as ex: + if ex.status == 404: + pass + else: + raise ex - if not self.module.check_mode: - try: - self.host_template = host_temp_api_instance.read_host_template( - cluster_name=self.cluster_name, - host_template_name=self.host_template_name, + if self.host_template: + if self.module._diff: + current = { + item.role_config_group_name + for item in self.host_template.role_config_group_refs + } + incoming = set(self.role_groups) + self.diff.update( + before=list(current - incoming), after=list(incoming - current) ) - except ApiException as ex: - if ex.status == 404: - pass - else: - raise ex - host_template = ApiHostTemplate( + if self.state == "present": + host_template_body = ApiHostTemplate( cluster_ref=ApiClusterRef( cluster_name=self.cluster_name, display_name=self.cluster_name ), - name=self.host_template_name, + name=self.name, role_config_group_refs=[ ApiRoleConfigGroupRef(role_config_group_name=group) - for group in self.role_configs_groups + for group in self.role_groups ], ) if self.host_template: - self.host_template_output = host_temp_api_instance.update_host_template( - cluster_name=self.cluster_name, - host_template_name=self.host_template_name, - body=host_template, - ) - self.changed = True + if not self.module.check_mode: + self.host_template_output = ( + host_temp_api_instance.update_host_template( + cluster_name=self.cluster_name, + host_template_name=self.name, + body=host_template_body, + ) + ) + self.changed = True else: - body = ApiHostTemplateList(items=[host_template]) - self.host_template_output = ( - host_temp_api_instance.create_host_templates( - cluster_name=self.cluster_name, body=body + body = ApiHostTemplateList(items=[host_template_body]) + if not self.module.check_mode: + self.host_template_output = ( + host_temp_api_instance.create_host_templates( + cluster_name=self.cluster_name, body=body + ) ) - ) - self.changed = True - + self.changed = True self.host_template_output = host_temp_api_instance.read_host_template( cluster_name=self.cluster_name, - host_template_name=self.host_template_name, + host_template_name=self.name, ).to_dict() + if self.state == "absent": + if not self.module.check_mode: + self.host_template_output = host_temp_api_instance.delete_host_template( + cluster_name=self.cluster_name, + host_template_name=self.name, + ).to_dict() + self.changed = True + def main(): module = ClouderaManagerModule.ansible_module( argument_spec=dict( - name=dict(required=True, type="str", aliases=["cluster_name"]), - host_template_name=dict(required=True, type="str"), - role_configs_groups=dict(required=True, type="list"), + cluster=dict(required=True, type="str", aliases=["cluster_name"]), + name=dict(required=True, type="str"), + role_groups=dict( + required=True, type="list", aliases=["role_config_groups"] + ), + state=dict( + type="str", + default="present", + choices=["present", "absent"], + ), ), supports_check_mode=True, ) @@ -197,6 +246,8 @@ def main(): changed=result.changed, host_template_output=result.host_template_output, ) + if module._diff: + output.update(diff=result.diff) if result.debug: log = result.log_capture.getvalue() diff --git a/plugins/modules/host_template_info.py b/plugins/modules/host_template_info.py index 3da3e129..8dbf6cdb 100644 --- a/plugins/modules/host_template_info.py +++ b/plugins/modules/host_template_info.py @@ -36,14 +36,14 @@ requirements: - cm_client options: - name: + cluster: description: - The associated cluster name. type: str required: yes aliases: - cluster_name - host_template_name: + name: description: - The name of the host template. type: str @@ -57,20 +57,20 @@ host: example.cloudera.com username: "jane_smith" password: "S&peR4Ec*re" - name: "cfm_cluster" - host_template_name: "cfm_host_template" + cluster: "cfm_cluster" + name: "cfm_host_template" - name: Retrieve the details about all host templates within the cluster cloudera.cluster.host_template_info host: example.cloudera.com username: "jane_smith" password: "S&peR4Ec*re" - name: "cfm_cluster" + cluster: "cfm_cluster" """ RETURN = r""" --- -cloudera_manager: +host_template_info: description: - Details about host template. type: list @@ -82,11 +82,11 @@ - The name of the host template type: str returned: always - clusterRef: + cluster_name: description: A reference to the enclosing cluster. type: dict returned: always - roleConfigGroupRefs: + role_config_group_refs: description: - The names of the role config groups type: list @@ -99,8 +99,8 @@ def __init__(self, module): super(ClouderaHostTemplateInfo, self).__init__(module) # Set the parameters - self.cluster_name = self.get_param("name") - self.host_template_name = self.get_param("host_template_name") + self.cluster_name = self.get_param("cluster") + self.name = self.get_param("name") # Initialize the return value self.host_templates_output = [] @@ -121,16 +121,16 @@ def process(self): raise ex host_temp_api_instance = HostTemplatesResourceApi(self.api_client) - if self.host_template_name: + if self.name: try: self.host_templates_output = host_temp_api_instance.read_host_template( cluster_name=self.cluster_name, - host_template_name=self.host_template_name, + host_template_name=self.name, ).to_dict() except ApiException as ex: if ex.status == 404: self.module.fail_json( - msg="Host Template does not exist: " + self.host_template_name + msg="Host Template does not exist: " + self.name ) else: raise ex @@ -144,8 +144,8 @@ def process(self): def main(): module = ClouderaManagerModule.ansible_module( argument_spec=dict( - name=dict(required=True, type="str", aliases=["cluster_name"]), - host_template_name=dict(required=False, type="str"), + cluster=dict(required=True, type="str", aliases=["cluster_name"]), + name=dict(required=False, type="str"), ), supports_check_mode=True, ) diff --git a/tests/unit/plugins/modules/host_template/test_host_template.py b/tests/unit/plugins/modules/host_template/test_host_template.py index ff2be452..f985bbb7 100644 --- a/tests/unit/plugins/modules/host_template/test_host_template.py +++ b/tests/unit/plugins/modules/host_template/test_host_template.py @@ -54,9 +54,9 @@ def conn(): def test_create_host_template(module_args, conn): conn.update( - name="TestCluster", - host_template_name="MyTemplate", - role_configs_groups=["atlas-ATLAS_SERVER-BASE", "atlas-GATEWAY-BASE"], + cluster="cloudera.cluster.example", + name="New_template", + role_groups=["atlas-ATLAS_SERVER-BASE", "atlas-GATEWAY-BASE"], ) module_args(conn) @@ -69,11 +69,10 @@ def test_create_host_template(module_args, conn): def test_update_host_template(module_args, conn): conn.update( - name="TestCluster", - host_template_name="MyTemplate", - role_configs_groups=[ + cluster="cloudera.cluster.example", + name="New_template", + role_groups=[ "atlas-ATLAS_SERVER-BASE", - "atlas-GATEWAY-BASE", "tez-GATEWAY-BASE", "hdfs-NAMENODE-BASE", ], @@ -85,3 +84,23 @@ def test_update_host_template(module_args, conn): host_template.main() LOG.info(str(e.value.host_template_output)) + + +def remove_template(module_args, conn): + conn.update( + cluster="Base-PVC", + name="4", + role_groups=[ + "atlas-ATLAS_SERVER-BASE", + "tez-GATEWAY-BASE", + "hdfs-NAMENODE-BASE", + ], + state="absent", + ) + + module_args(conn) + + with pytest.raises(AnsibleExitJson) as e: + host_template.main() + + LOG.info(str(e.value.host_template_output)) diff --git a/tests/unit/plugins/modules/host_template_info/test_host_template_info.py b/tests/unit/plugins/modules/host_template_info/test_host_template_info.py index 5a2a9cdb..d71c6c83 100644 --- a/tests/unit/plugins/modules/host_template_info/test_host_template_info.py +++ b/tests/unit/plugins/modules/host_template_info/test_host_template_info.py @@ -54,7 +54,7 @@ def conn(): def test_all_host_templates(module_args, conn): conn.update( - name="TestCluster", + cluster="cloudera.cluster.example", ) module_args(conn) @@ -67,8 +67,8 @@ def test_all_host_templates(module_args, conn): def test_single_host_template(module_args, conn): conn.update( - name="TestCluster", - host_template_name="template1", + cluster="cloudera.cluster.example", + name="MyTemplate13", ) module_args(conn) From aa6f6b9e1ee854ba725b32cc8d7dc68901e067cf Mon Sep 17 00:00:00 2001 From: rsuplina Date: Thu, 11 Jul 2024 16:21:04 +0100 Subject: [PATCH 5/9] Lint Updates Signed-off-by: rsuplina --- plugins/modules/host_template.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/modules/host_template.py b/plugins/modules/host_template.py index 8f811397..7de9aa30 100644 --- a/plugins/modules/host_template.py +++ b/plugins/modules/host_template.py @@ -37,7 +37,7 @@ module: host_template short_description: Configure a host template description: - - Creates a new host template or updates an existing one + - Creates a new host template or updates an existing one - The module supports C(check_mode). author: - "Ronald Suplina (@rsuplina)" @@ -72,7 +72,7 @@ EXAMPLES = r""" --- -- name: Create host template +- name: Create host template cloudera.cluster.host_template host: example.cloudera.com username: "jane_smith" @@ -81,7 +81,7 @@ name: "MyTemplate" role_groups: ["kafka-GATEWAY-BASE", "atlas-ATLAS_SERVER-BASE" , "hive_on_tez-GATEWAY-BASE"] -- name: Update host template +- name: Update host template cloudera.cluster.host_template host: example.cloudera.com username: "jane_smith" @@ -90,7 +90,7 @@ name: "MyTemplate" role_groups: ["kafka-GATEWAY-BASE", "atlas-ATLAS_SERVER-BASE"] -- name: Remove host template +- name: Remove host template cloudera.cluster.host_template host: example.cloudera.com username: "jane_smith" From 9559e549ae2d1c5429b08772df6674077228af2f Mon Sep 17 00:00:00 2001 From: rsuplina Date: Wed, 17 Jul 2024 18:14:01 +0100 Subject: [PATCH 6/9] Add parsed output Signed-off-by: rsuplina --- plugins/module_utils/host_template_utils.py | 41 +++++++++++++++++++++ plugins/modules/host_template.py | 29 +++++++-------- plugins/modules/host_template_info.py | 24 ++++++++---- 3 files changed, 71 insertions(+), 23 deletions(-) create mode 100644 plugins/module_utils/host_template_utils.py diff --git a/plugins/module_utils/host_template_utils.py b/plugins/module_utils/host_template_utils.py new file mode 100644 index 00000000..c04c5105 --- /dev/null +++ b/plugins/module_utils/host_template_utils.py @@ -0,0 +1,41 @@ +# Copyright 2024 Cloudera, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distribuFd under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +A common functions for Cloudera Manager host templates +""" + +HOST_TEMPLATE_OUTPUT = ["name", "cluster_ref", "role_config_group_refs"] + + +def _parse_host_template_output(host_template: dict) -> dict: + result = _parse_output(host_template, HOST_TEMPLATE_OUTPUT) + result["cluster_name"] = result["cluster_ref"]["cluster_name"] + result["role_groups"] = [ + role["role_config_group_name"] for role in result["role_config_group_refs"] + ] + del result["cluster_ref"] + del result["role_config_group_refs"] + return result + + +def _parse_host_templates_output(host_templates: list) -> list: + parsed_templates = [template.to_dict() for template in host_templates] + return [ + _parse_host_template_output(template_dict) for template_dict in parsed_templates + ] + + +def _parse_output(host_template: dict, keys: list) -> dict: + return {key: host_template[key] for key in keys if key in host_template} diff --git a/plugins/modules/host_template.py b/plugins/modules/host_template.py index 7de9aa30..9fa12101 100644 --- a/plugins/modules/host_template.py +++ b/plugins/modules/host_template.py @@ -15,7 +15,9 @@ from ansible_collections.cloudera.cluster.plugins.module_utils.cm_utils import ( ClouderaManagerModule, ) - +from ansible_collections.cloudera.cluster.plugins.module_utils.host_template_utils import ( + _parse_host_template_output, +) from cm_client import ( HostTemplatesResourceApi, ClustersResourceApi, @@ -118,7 +120,7 @@ description: A reference to the enclosing cluster. type: str returned: always - role_config_group_refs: + role_groups: description: - The role config groups belonging to this host tempalte. type: list @@ -192,34 +194,31 @@ def process(self): ) if self.host_template: if not self.module.check_mode: - self.host_template_output = ( - host_temp_api_instance.update_host_template( - cluster_name=self.cluster_name, - host_template_name=self.name, - body=host_template_body, - ) + host_temp_api_instance.update_host_template( + cluster_name=self.cluster_name, + host_template_name=self.name, + body=host_template_body, ) self.changed = True else: body = ApiHostTemplateList(items=[host_template_body]) if not self.module.check_mode: - self.host_template_output = ( - host_temp_api_instance.create_host_templates( + host_temp_api_instance.create_host_templates( cluster_name=self.cluster_name, body=body ) - ) self.changed = True - self.host_template_output = host_temp_api_instance.read_host_template( + + self.host_template_output = _parse_host_template_output(host_temp_api_instance.read_host_template( cluster_name=self.cluster_name, host_template_name=self.name, - ).to_dict() + ).to_dict()) if self.state == "absent": if not self.module.check_mode: - self.host_template_output = host_temp_api_instance.delete_host_template( + self.host_template_output = _parse_host_template_output(host_temp_api_instance.delete_host_template( cluster_name=self.cluster_name, host_template_name=self.name, - ).to_dict() + ).to_dict()) self.changed = True diff --git a/plugins/modules/host_template_info.py b/plugins/modules/host_template_info.py index 8dbf6cdb..23e4df00 100644 --- a/plugins/modules/host_template_info.py +++ b/plugins/modules/host_template_info.py @@ -17,6 +17,10 @@ ) from cm_client import HostTemplatesResourceApi, ClustersResourceApi from cm_client.rest import ApiException +from ansible_collections.cloudera.cluster.plugins.module_utils.host_template_utils import ( + _parse_host_template_output, + _parse_host_templates_output, +) ANSIBLE_METADATA = { "metadata_version": "1.1", @@ -86,7 +90,7 @@ description: A reference to the enclosing cluster. type: dict returned: always - role_config_group_refs: + role_groups: description: - The names of the role config groups type: list @@ -123,10 +127,12 @@ def process(self): host_temp_api_instance = HostTemplatesResourceApi(self.api_client) if self.name: try: - self.host_templates_output = host_temp_api_instance.read_host_template( - cluster_name=self.cluster_name, - host_template_name=self.name, - ).to_dict() + self.host_templates_output = _parse_host_template_output( + host_temp_api_instance.read_host_template( + cluster_name=self.cluster_name, + host_template_name=self.name, + ).to_dict() + ) except ApiException as ex: if ex.status == 404: self.module.fail_json( @@ -136,9 +142,11 @@ def process(self): raise ex else: - self.host_templates_output = host_temp_api_instance.read_host_templates( - cluster_name=self.cluster_name - ).items + self.host_templates_output = _parse_host_templates_output( + host_temp_api_instance.read_host_templates( + cluster_name=self.cluster_name + ).items + ) def main(): From 3865fdd699ec340649b4a67a2ae560866930107c Mon Sep 17 00:00:00 2001 From: rsuplina Date: Wed, 17 Jul 2024 18:16:36 +0100 Subject: [PATCH 7/9] Fix Linting issue Signed-off-by: rsuplina --- plugins/modules/host_template.py | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/plugins/modules/host_template.py b/plugins/modules/host_template.py index 9fa12101..b3757a24 100644 --- a/plugins/modules/host_template.py +++ b/plugins/modules/host_template.py @@ -204,21 +204,25 @@ def process(self): body = ApiHostTemplateList(items=[host_template_body]) if not self.module.check_mode: host_temp_api_instance.create_host_templates( - cluster_name=self.cluster_name, body=body - ) + cluster_name=self.cluster_name, body=body + ) self.changed = True - self.host_template_output = _parse_host_template_output(host_temp_api_instance.read_host_template( - cluster_name=self.cluster_name, - host_template_name=self.name, - ).to_dict()) + self.host_template_output = _parse_host_template_output( + host_temp_api_instance.read_host_template( + cluster_name=self.cluster_name, + host_template_name=self.name, + ).to_dict() + ) if self.state == "absent": if not self.module.check_mode: - self.host_template_output = _parse_host_template_output(host_temp_api_instance.delete_host_template( - cluster_name=self.cluster_name, - host_template_name=self.name, - ).to_dict()) + self.host_template_output = _parse_host_template_output( + host_temp_api_instance.delete_host_template( + cluster_name=self.cluster_name, + host_template_name=self.name, + ).to_dict() + ) self.changed = True From 3bdac0c88a1e6074c57c763856469915883232a0 Mon Sep 17 00:00:00 2001 From: rsuplina Date: Mon, 29 Jul 2024 16:24:08 +0100 Subject: [PATCH 8/9] Add requested updates Signed-off-by: rsuplina --- plugins/modules/host_template_info.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/plugins/modules/host_template_info.py b/plugins/modules/host_template_info.py index 23e4df00..3ea828df 100644 --- a/plugins/modules/host_template_info.py +++ b/plugins/modules/host_template_info.py @@ -94,6 +94,7 @@ description: - The names of the role config groups type: list + elements: str returned: always """ @@ -134,11 +135,7 @@ def process(self): ).to_dict() ) except ApiException as ex: - if ex.status == 404: - self.module.fail_json( - msg="Host Template does not exist: " + self.name - ) - else: + if ex.status != 404: raise ex else: From f001e8afe0ce92df3a3198ef8c080da66084b510 Mon Sep 17 00:00:00 2001 From: rsuplina Date: Tue, 30 Jul 2024 10:36:28 +0100 Subject: [PATCH 9/9] Add required_if Signed-off-by: rsuplina --- plugins/modules/host_template.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/plugins/modules/host_template.py b/plugins/modules/host_template.py index b3757a24..135e4c5f 100644 --- a/plugins/modules/host_template.py +++ b/plugins/modules/host_template.py @@ -232,7 +232,7 @@ def main(): cluster=dict(required=True, type="str", aliases=["cluster_name"]), name=dict(required=True, type="str"), role_groups=dict( - required=True, type="list", aliases=["role_config_groups"] + required=False, type="list", aliases=["role_config_groups"] ), state=dict( type="str", @@ -241,6 +241,9 @@ def main(): ), ), supports_check_mode=True, + required_if=[ + ("state", "present", ("cluster", "role_groups")), + ], ) result = ClouderaHostTemplate(module)