Skip to content

Commit

Permalink
Add Data Context modules (#246)
Browse files Browse the repository at this point in the history
Signed-off-by: rsuplina <rsuplina@cloudera.com>
  • Loading branch information
rsuplina authored Jul 29, 2024
1 parent 3900a7d commit 17bf83e
Show file tree
Hide file tree
Showing 5 changed files with 741 additions and 0 deletions.
46 changes: 46 additions & 0 deletions plugins/module_utils/data_context_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# 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 service management
"""

from ansible_collections.cloudera.cluster.plugins.module_utils.cm_utils import (
_parse_output,
)
from cm_client import ApiDataContextList


DATA_CONTEXT_OUTPUT = [
"name",
"display_name",
"nameservice",
"created_time",
"last_modified_time",
"services",
# "services_details",
"supported_service_types",
"allowed_cluster_versions",
"config_staleness_status",
"client_config_staleness_status",
"health_summary",
]


def _parse_output(data: dict, keys: list) -> dict:
return {key: data[key] for key in keys if key in data}


def parse_data_context_result(data_contexts: ApiDataContextList) -> list:
return [_parse_output(item, DATA_CONTEXT_OUTPUT) for item in data_contexts.items]
319 changes: 319 additions & 0 deletions plugins/modules/data_context.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,319 @@
# 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 (
ClouderaManagerMutableModule,
)
from cm_client import DataContextsResourceApi, ApiDataContextList

from cm_client import (
ClustersResourceApi,
ApiDataContext,
)
from cm_client.rest import ApiException
from ansible_collections.cloudera.cluster.plugins.module_utils.data_context_utils import (
parse_data_context_result,
)

ANSIBLE_METADATA = {
"metadata_version": "1.1",
"status": ["preview"],
"supported_by": "community",
}

DOCUMENTATION = r"""
---
module: data_context
short_description: Create, update, or delete a data context
description:
- Configure details of a specific data context.
- Create a new data context.
- Update an existing data context.
- Delete a data context.
- The module supports C(check_mode).
author:
- "Ronald Suplina (@rsuplina)"
requirements:
- cm-client >= 54
options:
name:
description:
- The name of the data context.
type: str
required: yes
aliases:
- context_name
- data_context_name
cluster:
description:
- The name of the Cloudera Manager cluster.
type: str
required: no
aliases:
- cluster_name
services:
description:
- A list of services that the data context will include.
type: list
required: no
state:
description:
- If I(state=present), the data context will be created or updated.
- If I(state=absent), the data context will be deleted
type: str
required: no
default: present
choices:
- present
- absent
attributes:
check_mode:
support: full
diff_mode:
support: full
"""

EXAMPLES = r"""
---
- name: Create a Data Context
cloudera.cluster.data_context
host: example.cloudera.com
username: "jane_smith"
password: "S&peR4Ec*re"
name: "base_services"
cluster: "example_cluster"
services: ['hive','atlas','hdfs','ranger']
state: present
- name: Delete a data context
cloudera.cluster.data_context
host: example.cloudera.com
username: "jane_smith"
password: "S&peR4Ec*re"
name: "base_services"
state: absent
- name: Update an existing data context
cloudera.cluster.data_context
host: example.cloudera.com
username: "jane_smith"
password: "S&peR4Ec*re"
name: "base_services"
cluster: "example_cluster"
services: ['hive','atlas','hdfs']
state: present
"""

RETURN = r"""
---
data_context:
description:
- A dictionary containing details of data contexts within the cluster.
type: dict
elements: complex
returned: always
contains:
name:
description:
- The name of the data context.
type: str
returned: always
display_name:
description:
- The display name of the data context.
type: str
returned: always
nameservice:
description:
- The name service that data context belongs to.
type: str
returned: always
created_time:
description:
- The timestamp indicating when the data context was created.
type: str
returned: always
last_modified_time:
description:
- The timestamp indicating the last modification of the data context.
type: str
returned: always
services:
description:
- The list of services associated with data context.
type: list
returned: always
supported_service_types:
description:
- The list of supported services types within data context.
type: list
returned: always
allowed_cluster_versions:
description:
- The list of allowed cluster versions within data context.
type: list
returned: always
config_staleness_status:
description:
- Status of the configuration within data context.
type: str
returned: always
client_config_staleness_status:
description:
- Status of the client configuration within data context.
type: str
returned: always
health_summary:
description:
- The health status of the data context.
type: str
returned: always
"""


class ClouderaDataContext(ClouderaManagerMutableModule):
def __init__(self, module):
super(ClouderaDataContext, self).__init__(module)

# Set the parameters
self.data_contex_name = self.get_param("name")
self.cluster_name = self.get_param("cluster")
self.services = self.get_param("services")
self.state = self.get_param("state")

# Initialize the return value
self.data_context_output = []
self.changed = False
self.diff = {}

# Execute the logic
self.process()

@ClouderaManagerMutableModule.handle_process
def process(self):
data_context_api = DataContextsResourceApi(self.api_client)
existing = []

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
try:
existing = data_context_api.read_data_context(
data_context_name=self.data_contex_name
).to_dict()
except ApiException as ex:
if (
ex.status == 500
): # Future change: Expected server response code will be 404
pass
else:
raise ex

if self.state == "present":
if existing:
existing_service = {
service["service_name"] for service in existing["services"]
}
incoming_service = set(self.services)
if existing_service != incoming_service:
if self.module._diff:
self.diff.update(
before=list(existing_service - incoming_service),
after=list(incoming_service - existing_service),
)
services = [
{"serviceName": service, "clusterName": self.cluster_name}
for service in incoming_service
]
if not self.module.check_mode:
update_data_context = data_context_api.update_data_context(
body=ApiDataContext(
name=self.data_contex_name, services=services
)
).to_dict()
self.data_context_output = parse_data_context_result(
ApiDataContextList(items=[update_data_context])
)
self.changed = True
else:
self.data_context_output = existing
else:
services = [
{"serviceName": service, "clusterName": self.cluster_name}
for service in self.services
]
if not self.module.check_mode:
create_data_context = data_context_api.create_data_context(
body=ApiDataContext(
name=self.data_contex_name, services=services
)
).to_dict()

self.data_context_output = parse_data_context_result(
ApiDataContextList(items=[create_data_context])
)
self.changed = True

if self.state == "absent":
if existing:
if not self.module.check_mode:
data_context_api.delete_data_context(
data_context_name=self.data_contex_name
).to_dict()
self.changed = True


def main():
module = ClouderaManagerMutableModule.ansible_module(
argument_spec=dict(
name=dict(
required=True, type="str", aliases=["context_name", "data_context_name"]
),
cluster=dict(required=False, type="str", aliases=["cluster_name"]),
services=dict(required=False, type="list"),
state=dict(
type="str",
default="present",
choices=["present", "absent"],
),
),
supports_check_mode=True,
required_if=[
("state", "present", ("cluster", "services"), False),
],
)
result = ClouderaDataContext(module)

output = dict(
changed=False,
data_context=result.data_context_output,
)
if module._diff:
output.update(diff=result.diff)

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()
Loading

0 comments on commit 17bf83e

Please sign in to comment.