From dd79a8fc9c0b3d89aaf784bade5a33c9e819f46a Mon Sep 17 00:00:00 2001 From: Saravanan Raju Date: Thu, 15 Jul 2021 12:05:06 +0530 Subject: [PATCH 1/8] Support CDW DBC and VW creation Signed-off-by: Saravanan Raju --- plugins/modules/dw_cluster.py | 20 +- plugins/modules/dw_dbc.py | 178 +++++++++++++++++ plugins/modules/dw_vw.py | 353 ++++++++++++++++++++++++++++++++++ 3 files changed, 547 insertions(+), 4 deletions(-) create mode 100644 plugins/modules/dw_dbc.py create mode 100644 plugins/modules/dw_vw.py diff --git a/plugins/modules/dw_cluster.py b/plugins/modules/dw_cluster.py index 2fa5f2ed..a38fa10d 100644 --- a/plugins/modules/dw_cluster.py +++ b/plugins/modules/dw_cluster.py @@ -47,6 +47,16 @@ aliases: - environment - env_crn + overlay: + description: Set it to true to save IP addresses in the VPC by using a private IP address range for Pods in the cluster. + type: bool + required: False + default: False + private_load_balancer: + description: Set up load balancer in private subnets. + type: bool + required: False + default: False aws_public_subnets: description: List of zero or more Public AWS Subnet IDs to deploy to type: list @@ -131,7 +141,7 @@ elements: complex contains: cluster: - tyoe: dict + type: dict contains: name: description: The name of the cluster. @@ -194,6 +204,7 @@ def __init__(self, module): self.name = self._get_param('name') self.env = self._get_param('env') self.overlay = self._get_param('overlay') + self.private_load_balancer = self._get_param('private_load_balancer') self.az_subnet = self._get_param('az_subnet') self.az_enable_az = self._get_param('az_enable_az') self.aws_public_subnets = self._get_param('aws_public_subnets') @@ -281,9 +292,9 @@ def process(self): self.module.fail_json(msg="Could not retrieve CRN for CDP Environment %s" % self.env) else: self.name = self.cdpy.dw.create_cluster( - env_crn=env_crn, overlay=self.overlay, aws_public_subnets=self.aws_public_subnets, - aws_private_subnets=self.aws_private_subnets, az_subnet=self.az_subnet, - az_enable_az=self.az_enable_az + env_crn=env_crn, overlay=self.overlay, private_load_balancer=self.private_load_balancer, + aws_public_subnets=self.aws_public_subnets, aws_private_subnets=self.aws_private_subnets, + az_subnet=self.az_subnet, az_enable_az=self.az_enable_az ) if self.wait: self.target = self.cdpy.sdk.wait_for_state( @@ -304,6 +315,7 @@ def main(): name=dict(required=False, type='str', aliases=['id']), env=dict(required=False, type='str', aliases=['environment', 'env_crn']), overlay=dict(required=False, type='bool', default=False), + private_load_balancer=dict(required=False, type='bool', default=False), az_subnet=dict(required=False, type='str', default=None), az_enable_az=dict(required=False, type='bool', default=None), aws_public_subnets=dict(required=False, type='list', default=None), diff --git a/plugins/modules/dw_dbc.py b/plugins/modules/dw_dbc.py new file mode 100644 index 00000000..6292becf --- /dev/null +++ b/plugins/modules/dw_dbc.py @@ -0,0 +1,178 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# Copyright 2021 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.module_utils.basic import AnsibleModule +from ansible_collections.cloudera.cloud.plugins.module_utils.cdp_common import CdpModule + +ANSIBLE_METADATA = {'metadata_version': '1.1', + 'status': ['preview'], + 'supported_by': 'community'} + +DOCUMENTATION = r''' +--- +module: dw_dbc +short_description: Create CDP Database Catalog +description: + - Create CDP Database Catalog +author: + - "Webster Mudge (@wmudge)" + - "Dan Chaffelson (@chaffelson)" +requirements: + - cdpy +options: + cluster_id: + description: ID of cluster where Database Catalog should be created. + type: str + required: True + name: + description: Name of the Database Catalog. + type: str + required: True + load_demo_data: + description: Set this to true if you demo data should be loaded into the Database Catalog. + type: str + required: False + wait: + description: + - Flag to enable internal polling to wait for the Data Catalog to achieve the declared state. + - If set to FALSE, the module will return immediately. + type: bool + required: False + default: True + delay: + description: + - The internal polling interval (in seconds) while the module waits for the Data Catalog to achieve the declared + state. + type: int + required: False + default: 15 + aliases: + - polling_delay + timeout: + description: + - The internal polling timeout (in seconds) while the module waits for the Data Catalog to achieve the declared + state. + type: int + required: False + default: 3600 + aliases: + - polling_timeout +extends_documentation_fragment: + - cloudera.cloud.cdp_sdk_options + - cloudera.cloud.cdp_auth_options +''' + +EXAMPLES = r''' +# Note: These examples do not set authentication details. + +# Create Database Catalog +- cloudera.cloud.dw_dbc: + name: example-database-catalog + cluster_id: example-cluster-id +''' + +RETURN = r''' +--- +dbcs: + description: The information about the named Database Catalog. + type: list + returned: always + elements: complex + contains: + id: + description: The id of the Database Catalog. + returned: always + type: str + name: + description: The name of the Database Catalog. + returned: always + type: str + status: + description: The status of the Database Catalog. + returned: always + type: str +sdk_out: + description: Returns the captured CDP SDK log. + returned: when supported + type: str +sdk_out_lines: + description: Returns a list of each line of the captured CDP SDK log. + returned: when supported + type: list + elements: str +''' + + +class DwDbc(CdpModule): + def __init__(self, module): + super(DwDbc, self).__init__(module) + + # Set variables + self.cluster_id = self._get_param('cluster_id') + self.name = self._get_param('name') + self.load_demo_data = self._get_param('load_demo_data') + self.state = self._get_param('state') + self.wait = self._get_param('wait') + self.delay = self._get_param('delay') + self.timeout = self._get_param('timeout') + + # Initialize return values + self.dbcs = [] + + # Execute logic process + self.process() + + @CdpModule._Decorators.process_debug + def process(self): + self.name = self.cdpy.dw.create_dbc(cluster_id=self.cluster_id, name=self.name, + load_demo_data=self.load_demo_data) + if self.wait: + self.target = self.cdpy.sdk.wait_for_state( + describe_func=self.cdpy.dw.describe_dbc, + params=dict(cluster_id=self.cluster_id, dbc_id=self.name['dbcId']), + state='Running', delay=self.delay, timeout=self.timeout + ) + else: + self.target = self.cdpy.dw.describe_dbc(cluster_id=self.cluster_id, dbc_id=self.name['dbcId']) + self.dbcs.append(self.target) + + +def main(): + module = AnsibleModule( + argument_spec=CdpModule.argument_spec( + cluster_id=dict(required=True, type='str', aliases=['cluster_id']), + name = dict(required=True, type='str', aliases=['name']), + load_demo_data=dict(required=False, type='bool', aliases=['load_demo_data']), + state=dict(required=False, type='str', choices=['present', 'absent'], default='present'), + wait = dict(required=False, type='bool', default=True), + delay = dict(required=False, type='int', aliases=['polling_delay'], default=15), + timeout = dict(required=False, type='int', aliases=['polling_timeout'], default=3600) + ), + supports_check_mode=True + ) + + result = DwDbc(module) + output = dict(changed=False, dbcs=result.dbcs) + + if result.debug: + output.update(sdk_out=result.log_out, sdk_out_lines=result.log_lines) + + module.exit_json(**output) + + +if __name__ == '__main__': + main() diff --git a/plugins/modules/dw_vw.py b/plugins/modules/dw_vw.py new file mode 100644 index 00000000..fef7163b --- /dev/null +++ b/plugins/modules/dw_vw.py @@ -0,0 +1,353 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# Copyright 2021 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.module_utils.basic import AnsibleModule +from ansible_collections.cloudera.cloud.plugins.module_utils.cdp_common import CdpModule + +ANSIBLE_METADATA = {'metadata_version': '1.1', + 'status': ['preview'], + 'supported_by': 'community'} + +DOCUMENTATION = r''' +--- +module: dw_vw +short_description: Create CDP Virtual Warehouse +description: + - Create CDP Virtual Warehouse +author: + - "Webster Mudge (@wmudge)" + - "Dan Chaffelson (@chaffelson)" +requirements: + - cdpy +options: + cluster_id: + description: ID of cluster where Virtual Warehouse should be created. + type: str + required: True + dbc_id: + description: ID of Database Catalog that the Virtual Warehouse should be attached to. + type: str + required: True + vw_type: + description: Type of Virtual Warehouse to be created. + type: str + required: True + name: + description: Name of the Virtual Warehouse. + type: str + required: True + template: + description: Name of configuration template to use. + type: str + required: False + autoscaling_min_cluster: + description: Minimum number of available nodes for Virtual Warehouse autoscaling. + type: int + required: False + autoscaling_max_cluster: + description: Maximum number of available nodes for Virtual Warehouse autoscaling. + type: int + required: False + config: + description: Configuration settings for the Virtual Warehouse. + type: dict + required: False + contains: + commonConfigs: + description: Configurations that are applied to every application in the service. + type: dict + required: False + contains: + configBlocks: List of ConfigBlocks for the application. + type: list + required: False + contains: + id: + description: ID of the ConfigBlock. Unique within an ApplicationConfig. + type: str + required: False + format: + description: Format of ConfigBlock. + type: str + required: False + content: + description: Contents of a ConfigBlock. + type: obj + required: False + contains: + keyValues: + description: Key-value type configurations. + type: obj + required: False + contains: + additionalProperties: + description: Key-value type configurations. + type: str + required: False + text: + description: Text type configuration. + type: str + required: False + json: + description: JSON type configuration. + type: str + required: False + applicationConfigs: + description: Application specific configurations. + type: dict + required: False + contains: + configBlocks: List of ConfigBlocks for the application. + type: list + required: False + contains: + id: + description: ID of the ConfigBlock. Unique within an ApplicationConfig. + type: str + required: False + format: + description: Format of ConfigBlock. + type: str + required: False + content: + description: Contents of a ConfigBlock. + type: obj + required: False + contains: + keyValues: + description: Key-value type configurations. + type: obj + required: False + contains: + additionalProperties: + description: Key-value type configurations. + type: str + required: False + text: + description: Text type configuration. + type: str + required: False + json: + description: JSON type configuration. + type: str + required: False + ldapGroups: + description: LDAP Groupnames to be enabled for auth. + type: list + required: False + enableSSO: + description: Should SSO be enabled for this VW. + type: bool + required: False + tags: + description: Tags associated with the resources. + type: dict + required: False + wait: + description: + - Flag to enable internal polling to wait for the Data Warehouse Cluster to achieve the declared state. + - If set to FALSE, the module will return immediately. + type: bool + required: False + default: True + delay: + description: + - The internal polling interval (in seconds) while the module waits for the Data Warehouse Cluster to achieve the declared + state. + type: int + required: False + default: 15 + aliases: + - polling_delay + timeout: + description: + - The internal polling timeout (in seconds) while the module waits for the Data Warehouse Cluster to achieve the declared + state. + type: int + required: False + default: 3600 + aliases: + - polling_timeout +extends_documentation_fragment: + - cloudera.cloud.cdp_sdk_options + - cloudera.cloud.cdp_auth_options +''' + +EXAMPLES = r''' +# Note: These examples do not set authentication details. + +# Create Virtual Warehouse +- cloudera.cloud.dw_vw: + name: "example-virtual-warehouse" + vw_type: "hive" + template: "xsmall" + autoscaling: + min_cluster: 3 + max_cluster: 19 + tags: + tag-key: "tag-value" + configs: + enable_sso: true + ldap_groups: ['group1','group2','group3'] +''' + +RETURN = r''' +--- +vws: + description: The information about the named CDW Virtual Warehouses. + type: list + returned: always + elements: complex + contains: + vws: + type: dict + contains: + id: + description: Id of the Virtual Warehouse created. + returned: always + type: str + name: + description: Name of the Virtual Warehouse created. + returned: always + type: str + vwType: + description: Virtual Warehouse type. + returned: always + type: str + dbcId: + description: Database Catalog ID against which Virtual Warehouse was created. + returned: always + type: str + creationDate: + description: The creation time of the cluster in UTC. + returned: always + type: str + status: + description: The status of the Virtual Warehouse. + returned: always + type: str + creator: + description: The CRN of the cluster creator. + returned: always + type: dict + contains: + crn: + type: str + description: Actor CRN + email: + type: str + description: Email address for users + workloadUsername: + type: str + description: Username for users + machineUsername: + type: str + description: Username for machine users + tags: + description: Custom tags that were used to create this Virtual Warehouse. + returned: always + type: dict +sdk_out: + description: Returns the captured CDP SDK log. + returned: when supported + type: str +sdk_out_lines: + description: Returns a list of each line of the captured CDP SDK log. + returned: when supported + type: list + elements: str +''' + + +class DwVw(CdpModule): + def __init__(self, module): + super(DwVw, self).__init__(module) + + # Set variables + self.cluster_id = self._get_param('cluster_id') + self.dbc_id = self._get_param('dbc_id') + self.vw_type = self._get_param('vw_type') + self.name = self._get_param('name') + self.template = self._get_param('template') + self.autoscaling_min_cluster = self._get_param('autoscaling_min_cluster') + self.autoscaling_max_cluster = self._get_param('autoscaling_max_cluster') + self.common_configs = self._get_param('common_configs') + self.application_configs = self._get_param('application_configs') + self.ldap_groups = self._get_param('ldap_groups') + self.enable_sso = self._get_param('enable_sso') + self.tags = self._get_param('tags') + self.wait = self._get_param('wait') + self.delay = self._get_param('delay') + self.timeout = self._get_param('timeout') + + # Initialize return values + self.vws = [] + + # Execute logic process + self.process() + + @CdpModule._Decorators.process_debug + def process(self): + self.name = self.cdpy.dw.create_vw(cluster_id=self.cluster_id, dbc_id=self.dbc_id, vw_type=self.vw_type, name=self.name, + template=self.template, autoscaling_min_cluster=self.autoscaling_min_cluster, + autoscaling_max_cluster=self.autoscaling_max_cluster, + common_configs=self.common_configs, application_configs=self.application_configs, + ldap_groups=self.ldap_groups, enable_sso=self.enable_sso, tags=self.tags) + if self.wait: + self.target = self.cdpy.sdk.wait_for_state( + describe_func=self.cdpy.dw.describe_vw, + params=dict(cluster_id=self.cluster_id, vw_id=self.name), + state='Running', delay=self.delay, timeout=self.timeout + ) + else: + self.target = self.cdpy.dw.describe_vw(cluster_id=self.cluster_id, vw_id=self.vw_id) + self.vws.append(self.target) + + +def main(): + module = AnsibleModule( + argument_spec=CdpModule.argument_spec( + cluster_id=dict(required=True, type='str', aliases=['cluster_id']), + dbc_id=dict(required=True, type='str', aliases=['dbc_id']), + vw_type = dict(required=True, type='str', aliases=['vw_type']), + name = dict(required=True, type='str', aliases=['name']), + template=dict(required=False, type='str', aliases=['template']), + autoscaling_min_cluster=dict(required=False, type='int', aliases=['autoscaling_min_cluster']), + autoscaling_max_cluster=dict(required=False, type='int', aliases=['autoscaling_max_cluster']), + common_configs=dict(required=False, type='dict', aliases=['common_configs']), + application_configs=dict(required=False, type='dict', aliases=['application_configs']), + ldap_groups=dict(required=False, type='list', aliases=['ldap_groups']), + enable_sso=dict(required=False, type='bool', aliases=['enable_sso']), + tags=dict(required=False, type='dict', aliases=['tags']), + wait = dict(required=False, type='bool', default=True), + delay = dict(required=False, type='int', aliases=['polling_delay'], default=15), + timeout = dict(required=False, type='int', aliases=['polling_timeout'], default=3600) + ), + supports_check_mode=True + ) + + result = DwVw(module) + output = dict(changed=False, vws=result.vws) + + if result.debug: + output.update(sdk_out=result.log_out, sdk_out_lines=result.log_lines) + + module.exit_json(**output) + + +if __name__ == '__main__': + main() From df6bff939f5de8bc25e1e950ad59cb1975bc4170 Mon Sep 17 00:00:00 2001 From: Saravanan Raju Date: Thu, 15 Jul 2021 12:28:24 +0530 Subject: [PATCH 2/8] Fix class docs Signed-off-by: Saravanan Raju --- plugins/modules/dw_dbc.py | 2 +- plugins/modules/dw_vw.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/modules/dw_dbc.py b/plugins/modules/dw_dbc.py index 6292becf..6184200e 100644 --- a/plugins/modules/dw_dbc.py +++ b/plugins/modules/dw_dbc.py @@ -25,7 +25,7 @@ DOCUMENTATION = r''' --- module: dw_dbc -short_description: Create CDP Database Catalog +short_description: Create CDP Data Warehouse Database Catalog description: - Create CDP Database Catalog author: diff --git a/plugins/modules/dw_vw.py b/plugins/modules/dw_vw.py index fef7163b..7d9be1d0 100644 --- a/plugins/modules/dw_vw.py +++ b/plugins/modules/dw_vw.py @@ -25,7 +25,7 @@ DOCUMENTATION = r''' --- module: dw_vw -short_description: Create CDP Virtual Warehouse +short_description: Create CDP Data Warehouse Virtual Warehouse description: - Create CDP Virtual Warehouse author: From aca177dfdee2bf0aa66fc70173a90e867c544c99 Mon Sep 17 00:00:00 2001 From: Saravanan Raju Date: Thu, 22 Jul 2021 10:54:24 +0530 Subject: [PATCH 3/8] Make vw and dbc modules idempotent Signed-off-by: Saravanan Raju --- plugins/modules/dw_dbc.py | 81 ++++++++++++++++++++++++++---- plugins/modules/dw_vw.py | 103 +++++++++++++++++++++++++++++++++----- 2 files changed, 162 insertions(+), 22 deletions(-) diff --git a/plugins/modules/dw_dbc.py b/plugins/modules/dw_dbc.py index 6184200e..d3804bc3 100644 --- a/plugins/modules/dw_dbc.py +++ b/plugins/modules/dw_dbc.py @@ -31,6 +31,7 @@ author: - "Webster Mudge (@wmudge)" - "Dan Chaffelson (@chaffelson)" + - "Saravanan Raju (@raju-saravanan)" requirements: - cdpy options: @@ -83,6 +84,12 @@ - cloudera.cloud.dw_dbc: name: example-database-catalog cluster_id: example-cluster-id + +# Delete Database Catalog +- cloudera.cloud.dw_dbc: + name: example-database-catalog + cluster_id: example-cluster-id + state: absent ''' RETURN = r''' @@ -133,22 +140,76 @@ def __init__(self, module): # Initialize return values self.dbcs = [] + # Initialize internal values + self.target = None + # Execute logic process self.process() @CdpModule._Decorators.process_debug def process(self): - self.name = self.cdpy.dw.create_dbc(cluster_id=self.cluster_id, name=self.name, - load_demo_data=self.load_demo_data) - if self.wait: - self.target = self.cdpy.sdk.wait_for_state( - describe_func=self.cdpy.dw.describe_dbc, - params=dict(cluster_id=self.cluster_id, dbc_id=self.name['dbcId']), - state='Running', delay=self.delay, timeout=self.timeout - ) + cluster = self.cdpy.dw.describe_cluster(cluster_id=self.cluster_id) + if cluster is None: + self.module.fail_json(msg="Couldn't retrieve cluster info for %s " % self.cluster_id) else: - self.target = self.cdpy.dw.describe_dbc(cluster_id=self.cluster_id, dbc_id=self.name['dbcId']) - self.dbcs.append(self.target) + self.target = self.cdpy.dw.describe_dbc(cluster_id=self.cluster_id, dbc_id=self.name) + # If Database Catalog exists + if self.target is not None: + if self.state == 'absent': + if self.module.check_mode: + self.clusters.append(self.target) + else: + if self.target['status'] not in self.cdpy.sdk.REMOVABLE_STATES: + self.module.warn( + "DW Database Catalog not in valid state for Delete operation: %s" % self.target['status']) + else: + _ = self.cdpy.dw.delete_dbc(cluster_id=self.cluster_id, dbc_id=self.name) + if self.wait: + self.cdpy.sdk.wait_for_state( + describe_func=self.cdpy.dw.describe_dbc, + params=dict(cluster_id=self.cluster_id, dbc_id=self.name), + field=None, delay=self.delay, timeout=self.timeout + ) + else: + self.cdpy.sdk.sleep(3) # Wait for consistency sync + self.target = self.cdpy.dw.describe_dbc(cluster_id=self.cluster_id, dbc_id=self.name) + self.clusters.append(self.target) + # Drop Done + elif self.state == 'present': + # Begin Config check + self.module.warn("DW Database Catalog already present and config validation is not implemented") + if self.wait: + self.target = self.cdpy.sdk.wait_for_state( + describe_func=self.cdpy.dw.describe_dbc, + params=dict(cluster_id=self.cluster_id,dbc_id=self.name), + state='Running', delay=self.delay, timeout=self.timeout + ) + self.clusters.append(self.target) + # End Config check + else: + self.module.fail_json(msg="State %s is not valid for this module" % self.state) + # End handling Database Catalog exists + else: + # Begin handling Database Catalog not found + if self.state == 'absent': + self.module.warn("DW Database Catalog %s already absent in Cluster %s" % (self.name, self.cluster_id)) + elif self.state == 'present': + if self.module.check_mode: + pass + else: + self.name = self.cdpy.dw.create_dbc(cluster_id=self.cluster_id, name=self.name, + load_demo_data=self.load_demo_data) + if self.wait: + self.target = self.cdpy.sdk.wait_for_state( + describe_func=self.cdpy.dw.describe_dbc, + params=dict(cluster_id=self.cluster_id, dbc_id=self.name['dbcId']), + state='Running', delay=self.delay, timeout=self.timeout + ) + else: + self.target = self.cdpy.dw.describe_dbc(cluster_id=self.cluster_id, dbc_id=self.name['dbcId']) + self.dbcs.append(self.target) + else: + self.module.fail_json(msg="State %s is not valid for this module" % self.state) def main(): diff --git a/plugins/modules/dw_vw.py b/plugins/modules/dw_vw.py index 7d9be1d0..c39d3237 100644 --- a/plugins/modules/dw_vw.py +++ b/plugins/modules/dw_vw.py @@ -31,6 +31,7 @@ author: - "Webster Mudge (@wmudge)" - "Dan Chaffelson (@chaffelson)" + - "Saravanan Raju (@raju-saravanan)" requirements: - cdpy options: @@ -157,6 +158,14 @@ description: Tags associated with the resources. type: dict required: False + state: + description: The declarative state of the Virtual Warehouse + type: str + required: False + default: present + choices: + - present + - absent wait: description: - Flag to enable internal polling to wait for the Data Warehouse Cluster to achieve the declared state. @@ -192,6 +201,7 @@ # Create Virtual Warehouse - cloudera.cloud.dw_vw: + cluster_id: "example-cluster-id" name: "example-virtual-warehouse" vw_type: "hive" template: "xsmall" @@ -203,6 +213,12 @@ configs: enable_sso: true ldap_groups: ['group1','group2','group3'] + +# Delete Virtual Warehouse +- cloudera.cloud.dw_vw: + cluster_id: "example-cluster-id" + name: "example-virtual-warehouse" + state: absent ''' RETURN = r''' @@ -289,6 +305,7 @@ def __init__(self, module): self.application_configs = self._get_param('application_configs') self.ldap_groups = self._get_param('ldap_groups') self.enable_sso = self._get_param('enable_sso') + self.state = self._get_param('state') self.tags = self._get_param('tags') self.wait = self._get_param('wait') self.delay = self._get_param('delay') @@ -297,33 +314,94 @@ def __init__(self, module): # Initialize return values self.vws = [] + # Initialize internal values + self.target = None + # Execute logic process self.process() @CdpModule._Decorators.process_debug def process(self): - self.name = self.cdpy.dw.create_vw(cluster_id=self.cluster_id, dbc_id=self.dbc_id, vw_type=self.vw_type, name=self.name, + cluster = self.cdpy.dw.describe_cluster(cluster_id=self.cluster_id) + if cluster is None: + self.module.fail_json(msg="Couldn't retrieve cluster info for %s " % self.cluster_id) + else: + dbc = self.cdpy.dw.describe_dbc(cluster_id=self.cluster_id, dbc_id=self.dbc_id) + if dbc is None: + self.module.fail_json(msg="Couldn't retrieve dbc info for %s " % self.dbc_id) + else: + self.target = self.cdpy.dw.describe_vw(cluster_id=self.cluster_id, vw_id=self.name) + # If Virtual Warehouse exists + if self.target is not None: + if self.state == 'absent': + if self.module.check_mode: + self.clusters.append(self.target) + else: + if self.target['status'] not in self.cdpy.sdk.REMOVABLE_STATES: + self.module.warn( + "DW Virtual Warehouse not in valid state for Delete operation: %s" % self.target[ + 'status']) + else: + _ = self.cdpy.dw.delete_vw(cluster_id=self.cluster_id, vw_id=self.name) + if self.wait: + self.cdpy.sdk.wait_for_state( + describe_func=self.cdpy.dw.describe_vw, + params=dict(cluster_id=self.cluster_id, vw_id=self.name), + field=None, delay=self.delay, timeout=self.timeout + ) + else: + self.cdpy.sdk.sleep(3) # Wait for consistency sync + self.target = self.cdpy.dw.describe_vw(cluster_id=self.cluster_id, vw_id=self.name) + self.clusters.append(self.target) + # Drop Done + elif self.state == 'present': + # Begin Config check + self.module.warn("DW Virtual Warehouse already present and config validation is not implemented") + if self.wait: + self.target = self.cdpy.sdk.wait_for_state( + describe_func=self.cdpy.dw.delete_vw, + params=dict(cluster_id=self.cluster_id, vw_id=self.name), + state='Running', delay=self.delay, timeout=self.timeout + ) + self.clusters.append(self.target) + # End Config check + else: + self.module.fail_json(msg="State %s is not valid for this module" % self.state) + # End handling Virtual Warehouse exists + else: + # Begin handling Virtual Warehouse not found + if self.state == 'absent': + self.module.warn( + "DW Virtual Warehouse %s already absent in Cluster %s" % (self.name, self.cluster_id)) + elif self.state == 'present': + if self.module.check_mode: + pass + else: + self.name = self.cdpy.dw.create_vw(cluster_id=self.cluster_id, + dbc_id=self.dbc_id, vw_type=self.vw_type, name=self.name, template=self.template, autoscaling_min_cluster=self.autoscaling_min_cluster, autoscaling_max_cluster=self.autoscaling_max_cluster, common_configs=self.common_configs, application_configs=self.application_configs, ldap_groups=self.ldap_groups, enable_sso=self.enable_sso, tags=self.tags) - if self.wait: - self.target = self.cdpy.sdk.wait_for_state( - describe_func=self.cdpy.dw.describe_vw, - params=dict(cluster_id=self.cluster_id, vw_id=self.name), - state='Running', delay=self.delay, timeout=self.timeout - ) - else: - self.target = self.cdpy.dw.describe_vw(cluster_id=self.cluster_id, vw_id=self.vw_id) - self.vws.append(self.target) + if self.wait: + self.target = self.cdpy.sdk.wait_for_state( + describe_func=self.cdpy.dw.describe_vw, + params=dict(cluster_id=self.cluster_id, vw_id=self.name), + state='Running', delay=self.delay, timeout=self.timeout + ) + else: + self.target = self.cdpy.dw.describe_vw(cluster_id=self.cluster_id, vw_id=self.vw_id) + self.vws.append(self.target) + else: + self.module.fail_json(msg="State %s is not valid for this module" % self.state) def main(): module = AnsibleModule( argument_spec=CdpModule.argument_spec( cluster_id=dict(required=True, type='str', aliases=['cluster_id']), - dbc_id=dict(required=True, type='str', aliases=['dbc_id']), - vw_type = dict(required=True, type='str', aliases=['vw_type']), + dbc_id=dict(required=False, type='str', aliases=['dbc_id']), + vw_type = dict(required=False, type='str', aliases=['vw_type']), name = dict(required=True, type='str', aliases=['name']), template=dict(required=False, type='str', aliases=['template']), autoscaling_min_cluster=dict(required=False, type='int', aliases=['autoscaling_min_cluster']), @@ -333,6 +411,7 @@ def main(): ldap_groups=dict(required=False, type='list', aliases=['ldap_groups']), enable_sso=dict(required=False, type='bool', aliases=['enable_sso']), tags=dict(required=False, type='dict', aliases=['tags']), + state=dict(required=False, type='str', choices=['present', 'absent'], default='present'), wait = dict(required=False, type='bool', default=True), delay = dict(required=False, type='int', aliases=['polling_delay'], default=15), timeout = dict(required=False, type='int', aliases=['polling_timeout'], default=3600) From 0c0fbdc5d612c31c1d93c74cedf06a85a527c7a2 Mon Sep 17 00:00:00 2001 From: Saravanan Raju Date: Thu, 22 Jul 2021 16:58:46 +0530 Subject: [PATCH 4/8] Make the dbc and vw module idempotent Signed-off-by: Saravanan Raju --- plugins/modules/dw_dbc.py | 135 ++++++++++++++++-------------- plugins/modules/dw_vw.py | 172 ++++++++++++++++++++------------------ 2 files changed, 162 insertions(+), 145 deletions(-) diff --git a/plugins/modules/dw_dbc.py b/plugins/modules/dw_dbc.py index d3804bc3..41ef2555 100644 --- a/plugins/modules/dw_dbc.py +++ b/plugins/modules/dw_dbc.py @@ -35,6 +35,11 @@ requirements: - cdpy options: + id: + description: + - If an ID is provided, that Database Catalog will be deleted if C(state=absent) + type: str + required: When state is absent cluster_id: description: ID of cluster where Database Catalog should be created. type: str @@ -82,14 +87,14 @@ # Create Database Catalog - cloudera.cloud.dw_dbc: - name: example-database-catalog - cluster_id: example-cluster-id + name: "example-database-catalog-name" + cluster_id: "example-cluster-id" # Delete Database Catalog - cloudera.cloud.dw_dbc: - name: example-database-catalog - cluster_id: example-cluster-id - state: absent + id: "example-database-id" + cluster_id: "example-cluster-id" + state: "absent" ''' RETURN = r''' @@ -129,6 +134,7 @@ def __init__(self, module): super(DwDbc, self).__init__(module) # Set variables + self.id = self._get_param('id') self.cluster_id = self._get_param('cluster_id') self.name = self._get_param('name') self.load_demo_data = self._get_param('load_demo_data') @@ -148,76 +154,79 @@ def __init__(self, module): @CdpModule._Decorators.process_debug def process(self): - cluster = self.cdpy.dw.describe_cluster(cluster_id=self.cluster_id) - if cluster is None: - self.module.fail_json(msg="Couldn't retrieve cluster info for %s " % self.cluster_id) - else: - self.target = self.cdpy.dw.describe_dbc(cluster_id=self.cluster_id, dbc_id=self.name) - # If Database Catalog exists - if self.target is not None: - if self.state == 'absent': - if self.module.check_mode: - self.clusters.append(self.target) + if self.id is None: + dbcs = self.cdpy.dw.list_dbcs(cluster_id=self.cluster_id) + for dbc in dbcs: + if self.name is not None and dbc['name'] == self.name: + self.target = self.cdpy.dw.describe_dbc(cluster_id=self.cluster_id, dbc_id=dbc['id']) + elif self.id is not None and dbc['id'] == self.id: + self.target = self.cdpy.dw.describe_dbc(cluster_id=self.cluster_id, dbc_id=self.id) + # If Database Catalog exists + if self.target is not None: + if self.state == 'absent': + if self.module.check_mode: + self.dbcs.append(self.target) + else: + if self.target['status'] not in self.cdpy.sdk.REMOVABLE_STATES: + self.module.warn( + "DW Database Catalog not in valid state for Delete operation: %s" % self.target['status']) else: - if self.target['status'] not in self.cdpy.sdk.REMOVABLE_STATES: - self.module.warn( - "DW Database Catalog not in valid state for Delete operation: %s" % self.target['status']) - else: - _ = self.cdpy.dw.delete_dbc(cluster_id=self.cluster_id, dbc_id=self.name) - if self.wait: - self.cdpy.sdk.wait_for_state( - describe_func=self.cdpy.dw.describe_dbc, - params=dict(cluster_id=self.cluster_id, dbc_id=self.name), - field=None, delay=self.delay, timeout=self.timeout - ) - else: - self.cdpy.sdk.sleep(3) # Wait for consistency sync - self.target = self.cdpy.dw.describe_dbc(cluster_id=self.cluster_id, dbc_id=self.name) - self.clusters.append(self.target) - # Drop Done - elif self.state == 'present': - # Begin Config check - self.module.warn("DW Database Catalog already present and config validation is not implemented") + _ = self.cdpy.dw.delete_dbc(cluster_id=self.cluster_id, dbc_id=self.target['id']) if self.wait: - self.target = self.cdpy.sdk.wait_for_state( + self.cdpy.sdk.wait_for_state( describe_func=self.cdpy.dw.describe_dbc, - params=dict(cluster_id=self.cluster_id,dbc_id=self.name), - state='Running', delay=self.delay, timeout=self.timeout + params=dict(cluster_id=self.cluster_id, dbc_id=self.target['id']), + field=None, delay=self.delay, timeout=self.timeout ) - self.clusters.append(self.target) - # End Config check - else: - self.module.fail_json(msg="State %s is not valid for this module" % self.state) - # End handling Database Catalog exists - else: - # Begin handling Database Catalog not found - if self.state == 'absent': - self.module.warn("DW Database Catalog %s already absent in Cluster %s" % (self.name, self.cluster_id)) - elif self.state == 'present': - if self.module.check_mode: - pass else: - self.name = self.cdpy.dw.create_dbc(cluster_id=self.cluster_id, name=self.name, - load_demo_data=self.load_demo_data) - if self.wait: - self.target = self.cdpy.sdk.wait_for_state( - describe_func=self.cdpy.dw.describe_dbc, - params=dict(cluster_id=self.cluster_id, dbc_id=self.name['dbcId']), - state='Running', delay=self.delay, timeout=self.timeout - ) - else: - self.target = self.cdpy.dw.describe_dbc(cluster_id=self.cluster_id, dbc_id=self.name['dbcId']) + self.cdpy.sdk.sleep(3) # Wait for consistency sync + self.target = self.cdpy.dw.describe_dbc(cluster_id=self.cluster_id, dbc_id=self.target['id']) self.dbcs.append(self.target) + # Drop Done + elif self.state == 'present': + # Begin Config check + self.module.warn("DW Database Catalog already present and config validation is not implemented") + if self.wait: + self.target = self.cdpy.sdk.wait_for_state( + describe_func=self.cdpy.dw.describe_dbc, + params=dict(cluster_id=self.cluster_id, dbc_id=self.target['id']), + state='Running', delay=self.delay, timeout=self.timeout + ) + self.dbcs.append(self.target) + # End Config check + else: + self.module.fail_json(msg="State %s is not valid for this module" % self.state) + # End handling Database Catalog exists + else: + # Begin handling Database Catalog not found + if self.state == 'absent': + self.module.warn("DW Database Catalog %s already absent in Cluster %s" % (self.name, self.cluster_id)) + elif self.state == 'present': + if self.module.check_mode: + pass else: - self.module.fail_json(msg="State %s is not valid for this module" % self.state) + dbc_id = self.cdpy.dw.create_dbc(cluster_id=self.cluster_id, name=self.name, + load_demo_data=self.load_demo_data) + if self.wait: + self.target = self.cdpy.sdk.wait_for_state( + describe_func=self.cdpy.dw.describe_dbc, + params=dict(cluster_id=self.cluster_id, dbc_id=dbc_id), + state='Running', delay=self.delay, timeout=self.timeout + ) + else: + self.target = self.cdpy.dw.describe_dbc(cluster_id=self.cluster_id, dbc_id=dbc_id) + self.dbcs.append(self.target) + else: + self.module.fail_json(msg="State %s is not valid for this module" % self.state) def main(): module = AnsibleModule( argument_spec=CdpModule.argument_spec( - cluster_id=dict(required=True, type='str', aliases=['cluster_id']), - name = dict(required=True, type='str', aliases=['name']), - load_demo_data=dict(required=False, type='bool', aliases=['load_demo_data']), + id=dict(required=False, type='str', default=None), + cluster_id=dict(required=True, type='str'), + name = dict(required=False, type='str', default=None), + load_demo_data=dict(required=False, type='bool', default=False), state=dict(required=False, type='str', choices=['present', 'absent'], default='present'), wait = dict(required=False, type='bool', default=True), delay = dict(required=False, type='int', aliases=['polling_delay'], default=15), diff --git a/plugins/modules/dw_vw.py b/plugins/modules/dw_vw.py index c39d3237..01e3835c 100644 --- a/plugins/modules/dw_vw.py +++ b/plugins/modules/dw_vw.py @@ -35,6 +35,11 @@ requirements: - cdpy options: + id: + description: + - If an ID is provided, that Virtual Warehouse will be deleted if C(state=absent) + type: str + required: When state is absent cluster_id: description: ID of cluster where Virtual Warehouse should be created. type: str @@ -217,7 +222,7 @@ # Delete Virtual Warehouse - cloudera.cloud.dw_vw: cluster_id: "example-cluster-id" - name: "example-virtual-warehouse" + id: "example-virtual-warehouse-id" state: absent ''' @@ -294,6 +299,7 @@ def __init__(self, module): super(DwVw, self).__init__(module) # Set variables + self.id = self._get_param('id') self.cluster_id = self._get_param('cluster_id') self.dbc_id = self._get_param('dbc_id') self.vw_type = self._get_param('vw_type') @@ -322,95 +328,97 @@ def __init__(self, module): @CdpModule._Decorators.process_debug def process(self): - cluster = self.cdpy.dw.describe_cluster(cluster_id=self.cluster_id) - if cluster is None: - self.module.fail_json(msg="Couldn't retrieve cluster info for %s " % self.cluster_id) - else: - dbc = self.cdpy.dw.describe_dbc(cluster_id=self.cluster_id, dbc_id=self.dbc_id) - if dbc is None: - self.module.fail_json(msg="Couldn't retrieve dbc info for %s " % self.dbc_id) - else: - self.target = self.cdpy.dw.describe_vw(cluster_id=self.cluster_id, vw_id=self.name) - # If Virtual Warehouse exists - if self.target is not None: - if self.state == 'absent': - if self.module.check_mode: - self.clusters.append(self.target) - else: - if self.target['status'] not in self.cdpy.sdk.REMOVABLE_STATES: - self.module.warn( - "DW Virtual Warehouse not in valid state for Delete operation: %s" % self.target[ - 'status']) - else: - _ = self.cdpy.dw.delete_vw(cluster_id=self.cluster_id, vw_id=self.name) - if self.wait: - self.cdpy.sdk.wait_for_state( - describe_func=self.cdpy.dw.describe_vw, - params=dict(cluster_id=self.cluster_id, vw_id=self.name), - field=None, delay=self.delay, timeout=self.timeout - ) - else: - self.cdpy.sdk.sleep(3) # Wait for consistency sync - self.target = self.cdpy.dw.describe_vw(cluster_id=self.cluster_id, vw_id=self.name) - self.clusters.append(self.target) - # Drop Done - elif self.state == 'present': - # Begin Config check - self.module.warn("DW Virtual Warehouse already present and config validation is not implemented") - if self.wait: - self.target = self.cdpy.sdk.wait_for_state( - describe_func=self.cdpy.dw.delete_vw, - params=dict(cluster_id=self.cluster_id, vw_id=self.name), - state='Running', delay=self.delay, timeout=self.timeout - ) - self.clusters.append(self.target) - # End Config check - else: - self.module.fail_json(msg="State %s is not valid for this module" % self.state) - # End handling Virtual Warehouse exists + if self.id is None: + vws = self.cdpy.dw.list_vws(cluster_id=self.cluster_id) + for vw in vws: + if self.name is not None and vw['name'] == self.name: + self.target = self.cdpy.dw.describe_vw(cluster_id=self.cluster_id, vw_id=vw['id']) + elif self.id is not None and vw['id'] == self.id: + self.target = self.cdpy.dw.describe_vw(cluster_id=self.cluster_id, vw_id=self.id) + # If Virtual Warehouse exists + if self.target is not None: + if self.state == 'absent': + if self.module.check_mode: + self.vws.append(self.target) else: - # Begin handling Virtual Warehouse not found - if self.state == 'absent': + if self.target['status'] not in self.cdpy.sdk.REMOVABLE_STATES: self.module.warn( - "DW Virtual Warehouse %s already absent in Cluster %s" % (self.name, self.cluster_id)) - elif self.state == 'present': - if self.module.check_mode: - pass - else: - self.name = self.cdpy.dw.create_vw(cluster_id=self.cluster_id, - dbc_id=self.dbc_id, vw_type=self.vw_type, name=self.name, - template=self.template, autoscaling_min_cluster=self.autoscaling_min_cluster, - autoscaling_max_cluster=self.autoscaling_max_cluster, - common_configs=self.common_configs, application_configs=self.application_configs, - ldap_groups=self.ldap_groups, enable_sso=self.enable_sso, tags=self.tags) - if self.wait: - self.target = self.cdpy.sdk.wait_for_state( - describe_func=self.cdpy.dw.describe_vw, - params=dict(cluster_id=self.cluster_id, vw_id=self.name), - state='Running', delay=self.delay, timeout=self.timeout - ) - else: - self.target = self.cdpy.dw.describe_vw(cluster_id=self.cluster_id, vw_id=self.vw_id) - self.vws.append(self.target) + "DW Virtual Warehouse not in valid state for Delete operation: %s" % self.target[ + 'status']) else: - self.module.fail_json(msg="State %s is not valid for this module" % self.state) + _ = self.cdpy.dw.delete_vw(cluster_id=self.cluster_id, vw_id=self.target['id']) + if self.wait: + self.cdpy.sdk.wait_for_state( + describe_func=self.cdpy.dw.describe_vw, + params=dict(cluster_id=self.cluster_id, vw_id=self.target['id']), + field=None, delay=self.delay, timeout=self.timeout + ) + else: + self.cdpy.sdk.sleep(3) # Wait for consistency sync + self.target = self.cdpy.dw.describe_vw(cluster_id=self.cluster_id, vw_id=self.target['id']) + self.vws.append(self.target) + # Drop Done + elif self.state == 'present': + # Begin Config check + self.module.warn("DW Virtual Warehouse already present and config validation is not implemented") + if self.wait: + self.target = self.cdpy.sdk.wait_for_state( + describe_func=self.cdpy.dw.delete_vw, + params=dict(cluster_id=self.cluster_id, vw_id=self.target['id']), + state='Running', delay=self.delay, timeout=self.timeout + ) + self.vws.append(self.target) + # End Config check + else: + self.module.fail_json(msg="State %s is not valid for this module" % self.state) + # End handling Virtual Warehouse exists + else: + # Begin handling Virtual Warehouse not found + if self.state == 'absent': + self.module.warn( + "DW Virtual Warehouse %s already absent in Cluster %s" % (self.name, self.cluster_id)) + elif self.state == 'present': + if self.module.check_mode: + pass + else: + vw_id = self.cdpy.dw.create_vw(cluster_id=self.cluster_id, + dbc_id=self.dbc_id, vw_type=self.vw_type, name=self.name, + template=self.template, + autoscaling_min_cluster=self.autoscaling_min_cluster, + autoscaling_max_cluster=self.autoscaling_max_cluster, + common_configs=self.common_configs, + application_configs=self.application_configs, + ldap_groups=self.ldap_groups, enable_sso=self.enable_sso, + tags=self.tags) + if self.wait: + self.target = self.cdpy.sdk.wait_for_state( + describe_func=self.cdpy.dw.describe_vw, + params=dict(cluster_id=self.cluster_id, vw_id=vw_id), + state='Running', delay=self.delay, timeout=self.timeout + ) + else: + self.target = self.cdpy.dw.describe_vw(cluster_id=self.cluster_id, vw_id=vw_id) + self.vws.append(self.target) + else: + self.module.fail_json(msg="State %s is not valid for this module" % self.state) def main(): module = AnsibleModule( argument_spec=CdpModule.argument_spec( - cluster_id=dict(required=True, type='str', aliases=['cluster_id']), - dbc_id=dict(required=False, type='str', aliases=['dbc_id']), - vw_type = dict(required=False, type='str', aliases=['vw_type']), - name = dict(required=True, type='str', aliases=['name']), - template=dict(required=False, type='str', aliases=['template']), - autoscaling_min_cluster=dict(required=False, type='int', aliases=['autoscaling_min_cluster']), - autoscaling_max_cluster=dict(required=False, type='int', aliases=['autoscaling_max_cluster']), - common_configs=dict(required=False, type='dict', aliases=['common_configs']), - application_configs=dict(required=False, type='dict', aliases=['application_configs']), - ldap_groups=dict(required=False, type='list', aliases=['ldap_groups']), - enable_sso=dict(required=False, type='bool', aliases=['enable_sso']), - tags=dict(required=False, type='dict', aliases=['tags']), + id=dict(required=False, type='str', default=None), + cluster_id=dict(required=True, type='str'), + dbc_id=dict(required=False, type='str', default=None), + vw_type = dict(required=False, type='str', default=None), + name = dict(required=False, type='str', default=None), + template=dict(required=False, type='str', default=None), + autoscaling_min_cluster=dict(required=False, type='int', default=None), + autoscaling_max_cluster=dict(required=False, type='int', default=None), + common_configs=dict(required=False, type='dict', default=None), + application_configs=dict(required=False, type='dict', default=None), + ldap_groups=dict(required=False, type='list', default=None), + enable_sso=dict(required=False, type='bool', default=False), + tags=dict(required=False, type='dict', default=None), state=dict(required=False, type='str', choices=['present', 'absent'], default='present'), wait = dict(required=False, type='bool', default=True), delay = dict(required=False, type='int', aliases=['polling_delay'], default=15), From 71fdbb75068bb296831a120277bffc141a198ba6 Mon Sep 17 00:00:00 2001 From: Saravanan Raju Date: Thu, 22 Jul 2021 17:17:48 +0530 Subject: [PATCH 5/8] Use started state in wait for state function Signed-off-by: Saravanan Raju --- plugins/modules/dw_dbc.py | 4 ++-- plugins/modules/dw_vw.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/modules/dw_dbc.py b/plugins/modules/dw_dbc.py index 41ef2555..8bb9d046 100644 --- a/plugins/modules/dw_dbc.py +++ b/plugins/modules/dw_dbc.py @@ -190,7 +190,7 @@ def process(self): self.target = self.cdpy.sdk.wait_for_state( describe_func=self.cdpy.dw.describe_dbc, params=dict(cluster_id=self.cluster_id, dbc_id=self.target['id']), - state='Running', delay=self.delay, timeout=self.timeout + state=self.cdpy.sdk.STARTED_STATES, delay=self.delay, timeout=self.timeout ) self.dbcs.append(self.target) # End Config check @@ -211,7 +211,7 @@ def process(self): self.target = self.cdpy.sdk.wait_for_state( describe_func=self.cdpy.dw.describe_dbc, params=dict(cluster_id=self.cluster_id, dbc_id=dbc_id), - state='Running', delay=self.delay, timeout=self.timeout + state=self.cdpy.sdk.STARTED_STATES, delay=self.delay, timeout=self.timeout ) else: self.target = self.cdpy.dw.describe_dbc(cluster_id=self.cluster_id, dbc_id=dbc_id) diff --git a/plugins/modules/dw_vw.py b/plugins/modules/dw_vw.py index 01e3835c..bf19b8b9 100644 --- a/plugins/modules/dw_vw.py +++ b/plugins/modules/dw_vw.py @@ -365,7 +365,7 @@ def process(self): self.target = self.cdpy.sdk.wait_for_state( describe_func=self.cdpy.dw.delete_vw, params=dict(cluster_id=self.cluster_id, vw_id=self.target['id']), - state='Running', delay=self.delay, timeout=self.timeout + state=self.cdpy.sdk.STARTED_STATES, delay=self.delay, timeout=self.timeout ) self.vws.append(self.target) # End Config check @@ -394,7 +394,7 @@ def process(self): self.target = self.cdpy.sdk.wait_for_state( describe_func=self.cdpy.dw.describe_vw, params=dict(cluster_id=self.cluster_id, vw_id=vw_id), - state='Running', delay=self.delay, timeout=self.timeout + state=self.cdpy.sdk.STARTED_STATES, delay=self.delay, timeout=self.timeout ) else: self.target = self.cdpy.dw.describe_vw(cluster_id=self.cluster_id, vw_id=vw_id) From 6e504d3efa38865f59c527fdceb8e0a57b78d2e0 Mon Sep 17 00:00:00 2001 From: Saravanan Raju Date: Thu, 22 Jul 2021 18:45:25 +0530 Subject: [PATCH 6/8] Fix typo Signed-off-by: Saravanan Raju --- plugins/modules/dw_vw.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/modules/dw_vw.py b/plugins/modules/dw_vw.py index bf19b8b9..4491c9b3 100644 --- a/plugins/modules/dw_vw.py +++ b/plugins/modules/dw_vw.py @@ -363,7 +363,7 @@ def process(self): self.module.warn("DW Virtual Warehouse already present and config validation is not implemented") if self.wait: self.target = self.cdpy.sdk.wait_for_state( - describe_func=self.cdpy.dw.delete_vw, + describe_func=self.cdpy.dw.describe_vw, params=dict(cluster_id=self.cluster_id, vw_id=self.target['id']), state=self.cdpy.sdk.STARTED_STATES, delay=self.delay, timeout=self.timeout ) From a9eb2bec9259bf53a5596ea24169af8e5ac28c11 Mon Sep 17 00:00:00 2001 From: Saravanan Raju Date: Fri, 23 Jul 2021 15:24:10 +0530 Subject: [PATCH 7/8] Rename vh and dbc module Signed-off-by: Saravanan Raju --- .../{dw_dbc.py => dw_database_catalog.py} | 22 +- .../{dw_vw.py => dw_virtual_warehouse.py} | 225 +++++++++--------- 2 files changed, 120 insertions(+), 127 deletions(-) rename plugins/modules/{dw_dbc.py => dw_database_catalog.py} (93%) rename plugins/modules/{dw_vw.py => dw_virtual_warehouse.py} (73%) diff --git a/plugins/modules/dw_dbc.py b/plugins/modules/dw_database_catalog.py similarity index 93% rename from plugins/modules/dw_dbc.py rename to plugins/modules/dw_database_catalog.py index 8bb9d046..bfecbe4e 100644 --- a/plugins/modules/dw_dbc.py +++ b/plugins/modules/dw_database_catalog.py @@ -24,7 +24,7 @@ DOCUMENTATION = r''' --- -module: dw_dbc +module: dw_database_catalog short_description: Create CDP Data Warehouse Database Catalog description: - Create CDP Database Catalog @@ -86,12 +86,12 @@ # Note: These examples do not set authentication details. # Create Database Catalog -- cloudera.cloud.dw_dbc: +- cloudera.cloud.dw_database_catalog: name: "example-database-catalog-name" cluster_id: "example-cluster-id" # Delete Database Catalog -- cloudera.cloud.dw_dbc: +- cloudera.cloud.dw_database_catalog: id: "example-database-id" cluster_id: "example-cluster-id" state: "absent" @@ -99,7 +99,7 @@ RETURN = r''' --- -dbcs: +database_catalogs: description: The information about the named Database Catalog. type: list returned: always @@ -144,7 +144,7 @@ def __init__(self, module): self.timeout = self._get_param('timeout') # Initialize return values - self.dbcs = [] + self.database_catalogs = [] # Initialize internal values self.target = None @@ -165,7 +165,7 @@ def process(self): if self.target is not None: if self.state == 'absent': if self.module.check_mode: - self.dbcs.append(self.target) + self.database_catalogs.append(self.target) else: if self.target['status'] not in self.cdpy.sdk.REMOVABLE_STATES: self.module.warn( @@ -179,9 +179,9 @@ def process(self): field=None, delay=self.delay, timeout=self.timeout ) else: - self.cdpy.sdk.sleep(3) # Wait for consistency sync + self.cdpy.sdk.sleep(self.delay) # Wait for consistency sync self.target = self.cdpy.dw.describe_dbc(cluster_id=self.cluster_id, dbc_id=self.target['id']) - self.dbcs.append(self.target) + self.database_catalogs.append(self.target) # Drop Done elif self.state == 'present': # Begin Config check @@ -192,7 +192,7 @@ def process(self): params=dict(cluster_id=self.cluster_id, dbc_id=self.target['id']), state=self.cdpy.sdk.STARTED_STATES, delay=self.delay, timeout=self.timeout ) - self.dbcs.append(self.target) + self.database_catalogs.append(self.target) # End Config check else: self.module.fail_json(msg="State %s is not valid for this module" % self.state) @@ -215,7 +215,7 @@ def process(self): ) else: self.target = self.cdpy.dw.describe_dbc(cluster_id=self.cluster_id, dbc_id=dbc_id) - self.dbcs.append(self.target) + self.database_catalogs.append(self.target) else: self.module.fail_json(msg="State %s is not valid for this module" % self.state) @@ -236,7 +236,7 @@ def main(): ) result = DwDbc(module) - output = dict(changed=False, dbcs=result.dbcs) + output = dict(changed=False, database_catalogs=result.database_catalogs) if result.debug: output.update(sdk_out=result.log_out, sdk_out_lines=result.log_lines) diff --git a/plugins/modules/dw_vw.py b/plugins/modules/dw_virtual_warehouse.py similarity index 73% rename from plugins/modules/dw_vw.py rename to plugins/modules/dw_virtual_warehouse.py index 4491c9b3..9ba59041 100644 --- a/plugins/modules/dw_vw.py +++ b/plugins/modules/dw_virtual_warehouse.py @@ -24,7 +24,7 @@ DOCUMENTATION = r''' --- -module: dw_vw +module: dw_virtual_warehouse short_description: Create CDP Data Warehouse Virtual Warehouse description: - Create CDP Virtual Warehouse @@ -48,7 +48,7 @@ description: ID of Database Catalog that the Virtual Warehouse should be attached to. type: str required: True - vw_type: + type: description: Type of Virtual Warehouse to be created. type: str required: True @@ -60,105 +60,100 @@ description: Name of configuration template to use. type: str required: False - autoscaling_min_cluster: + autoscaling_min_nodes: description: Minimum number of available nodes for Virtual Warehouse autoscaling. type: int required: False - autoscaling_max_cluster: + autoscaling_max_nodes: description: Maximum number of available nodes for Virtual Warehouse autoscaling. type: int required: False - config: - description: Configuration settings for the Virtual Warehouse. + common_configs: + description: Configurations that are applied to every application in the service. type: dict required: False contains: - commonConfigs: - description: Configurations that are applied to every application in the service. - type: dict - required: False + configBlocks: List of ConfigBlocks for the application. + type: list + required: False contains: - configBlocks: List of ConfigBlocks for the application. - type: list - required: False - contains: - id: - description: ID of the ConfigBlock. Unique within an ApplicationConfig. - type: str - required: False - format: - description: Format of ConfigBlock. - type: str - required: False - content: - description: Contents of a ConfigBlock. - type: obj - required: False - contains: - keyValues: - description: Key-value type configurations. - type: obj - required: False - contains: - additionalProperties: - description: Key-value type configurations. - type: str - required: False - text: - description: Text type configuration. - type: str - required: False - json: - description: JSON type configuration. - type: str - required: False - applicationConfigs: - description: Application specific configurations. - type: dict - required: False - contains: - configBlocks: List of ConfigBlocks for the application. - type: list - required: False - contains: - id: - description: ID of the ConfigBlock. Unique within an ApplicationConfig. - type: str - required: False - format: - description: Format of ConfigBlock. - type: str - required: False - content: - description: Contents of a ConfigBlock. - type: obj - required: False - contains: - keyValues: - description: Key-value type configurations. - type: obj - required: False - contains: - additionalProperties: - description: Key-value type configurations. - type: str - required: False - text: - description: Text type configuration. - type: str - required: False - json: - description: JSON type configuration. - type: str - required: False - ldapGroups: - description: LDAP Groupnames to be enabled for auth. + id: + description: ID of the ConfigBlock. Unique within an ApplicationConfig. + type: str + required: False + format: + description: Format of ConfigBlock. + type: str + required: False + content: + description: Contents of a ConfigBlock. + type: obj + required: False + contains: + keyValues: + description: Key-value type configurations. + type: obj + required: False + contains: + additionalProperties: + description: Key-value type configurations. + type: str + required: False + text: + description: Text type configuration. + type: str + required: False + json: + description: JSON type configuration. + type: str + required: False + application_configs: + description: Configurations that are applied to every application in the service. + type: dict + required: False + contains: + configBlocks: List of ConfigBlocks for the application. type: list - required: False - enableSSO: - description: Should SSO be enabled for this VW. - type: bool - required: False + required: False + contains: + id: + description: ID of the ConfigBlock. Unique within an ApplicationConfig. + type: str + required: False + format: + description: Format of ConfigBlock. + type: str + required: False + content: + description: Contents of a ConfigBlock. + type: obj + required: False + contains: + keyValues: + description: Key-value type configurations. + type: obj + required: False + contains: + additionalProperties: + description: Key-value type configurations. + type: str + required: False + text: + description: Text type configuration. + type: str + required: False + json: + description: JSON type configuration. + type: str + required: False + ldap_groups: + description: LDAP Groupnames to be enabled for auth. + type: list + required: False + enable_sso: + description: Should SSO be enabled for this VW. + type: bool + required: False tags: description: Tags associated with the resources. type: dict @@ -205,22 +200,20 @@ # Note: These examples do not set authentication details. # Create Virtual Warehouse -- cloudera.cloud.dw_vw: +- cloudera.cloud.dw_virtual_warehouse: cluster_id: "example-cluster-id" name: "example-virtual-warehouse" - vw_type: "hive" + type: "hive" template: "xsmall" - autoscaling: - min_cluster: 3 - max_cluster: 19 + autoscaling_min_nodes: 3 + autoscaling_max_nodes: 19 tags: tag-key: "tag-value" - configs: - enable_sso: true - ldap_groups: ['group1','group2','group3'] + enable_sso: true + ldap_groups: ['group1','group2','group3'] # Delete Virtual Warehouse -- cloudera.cloud.dw_vw: +- cloudera.cloud.dw_virtual_warehouse: cluster_id: "example-cluster-id" id: "example-virtual-warehouse-id" state: absent @@ -228,7 +221,7 @@ RETURN = r''' --- -vws: +virtual_warehouses: description: The information about the named CDW Virtual Warehouses. type: list returned: always @@ -302,11 +295,11 @@ def __init__(self, module): self.id = self._get_param('id') self.cluster_id = self._get_param('cluster_id') self.dbc_id = self._get_param('dbc_id') - self.vw_type = self._get_param('vw_type') + self.type = self._get_param('type') self.name = self._get_param('name') self.template = self._get_param('template') - self.autoscaling_min_cluster = self._get_param('autoscaling_min_cluster') - self.autoscaling_max_cluster = self._get_param('autoscaling_max_cluster') + self.autoscaling_min_nodes = self._get_param('autoscaling_min_nodes') + self.autoscaling_max_nodes = self._get_param('autoscaling_max_nodes') self.common_configs = self._get_param('common_configs') self.application_configs = self._get_param('application_configs') self.ldap_groups = self._get_param('ldap_groups') @@ -318,7 +311,7 @@ def __init__(self, module): self.timeout = self._get_param('timeout') # Initialize return values - self.vws = [] + self.virtual_warehouses = [] # Initialize internal values self.target = None @@ -339,7 +332,7 @@ def process(self): if self.target is not None: if self.state == 'absent': if self.module.check_mode: - self.vws.append(self.target) + self.virtual_warehouses.append(self.target) else: if self.target['status'] not in self.cdpy.sdk.REMOVABLE_STATES: self.module.warn( @@ -354,9 +347,9 @@ def process(self): field=None, delay=self.delay, timeout=self.timeout ) else: - self.cdpy.sdk.sleep(3) # Wait for consistency sync + self.cdpy.sdk.sleep(self.delay) # Wait for consistency sync self.target = self.cdpy.dw.describe_vw(cluster_id=self.cluster_id, vw_id=self.target['id']) - self.vws.append(self.target) + self.virtual_warehouses.append(self.target) # Drop Done elif self.state == 'present': # Begin Config check @@ -367,7 +360,7 @@ def process(self): params=dict(cluster_id=self.cluster_id, vw_id=self.target['id']), state=self.cdpy.sdk.STARTED_STATES, delay=self.delay, timeout=self.timeout ) - self.vws.append(self.target) + self.virtual_warehouses.append(self.target) # End Config check else: self.module.fail_json(msg="State %s is not valid for this module" % self.state) @@ -382,10 +375,10 @@ def process(self): pass else: vw_id = self.cdpy.dw.create_vw(cluster_id=self.cluster_id, - dbc_id=self.dbc_id, vw_type=self.vw_type, name=self.name, + dbc_id=self.dbc_id, vw_type=self.type, name=self.name, template=self.template, - autoscaling_min_cluster=self.autoscaling_min_cluster, - autoscaling_max_cluster=self.autoscaling_max_cluster, + autoscaling_min_cluster=self.autoscaling_min_nodes, + autoscaling_max_cluster=self.autoscaling_max_nodes, common_configs=self.common_configs, application_configs=self.application_configs, ldap_groups=self.ldap_groups, enable_sso=self.enable_sso, @@ -398,7 +391,7 @@ def process(self): ) else: self.target = self.cdpy.dw.describe_vw(cluster_id=self.cluster_id, vw_id=vw_id) - self.vws.append(self.target) + self.virtual_warehouses.append(self.target) else: self.module.fail_json(msg="State %s is not valid for this module" % self.state) @@ -409,11 +402,11 @@ def main(): id=dict(required=False, type='str', default=None), cluster_id=dict(required=True, type='str'), dbc_id=dict(required=False, type='str', default=None), - vw_type = dict(required=False, type='str', default=None), + type = dict(required=False, type='str', default=None), name = dict(required=False, type='str', default=None), template=dict(required=False, type='str', default=None), - autoscaling_min_cluster=dict(required=False, type='int', default=None), - autoscaling_max_cluster=dict(required=False, type='int', default=None), + autoscaling_min_nodes=dict(required=False, type='int', default=None), + autoscaling_max_nodes=dict(required=False, type='int', default=None), common_configs=dict(required=False, type='dict', default=None), application_configs=dict(required=False, type='dict', default=None), ldap_groups=dict(required=False, type='list', default=None), @@ -428,7 +421,7 @@ def main(): ) result = DwVw(module) - output = dict(changed=False, vws=result.vws) + output = dict(changed=False, virtual_warehouses=result.virtual_warehouses) if result.debug: output.update(sdk_out=result.log_out, sdk_out_lines=result.log_lines) From 7032e26991f989f92a739230370e17eccf884be2 Mon Sep 17 00:00:00 2001 From: Saravanan Raju Date: Tue, 27 Jul 2021 22:22:52 +0530 Subject: [PATCH 8/8] Expand common_config and application_config Signed-off-by: Saravanan Raju --- plugins/modules/dw_database_catalog.py | 21 +-- plugins/modules/dw_virtual_warehouse.py | 188 +++++++++++++++--------- 2 files changed, 134 insertions(+), 75 deletions(-) diff --git a/plugins/modules/dw_database_catalog.py b/plugins/modules/dw_database_catalog.py index bfecbe4e..8bee6c68 100644 --- a/plugins/modules/dw_database_catalog.py +++ b/plugins/modules/dw_database_catalog.py @@ -104,7 +104,7 @@ type: list returned: always elements: complex - contains: + suboptions: id: description: The id of the Database Catalog. returned: always @@ -148,6 +148,7 @@ def __init__(self, module): # Initialize internal values self.target = None + self.changed = False # Execute logic process self.process() @@ -172,6 +173,7 @@ def process(self): "DW Database Catalog not in valid state for Delete operation: %s" % self.target['status']) else: _ = self.cdpy.dw.delete_dbc(cluster_id=self.cluster_id, dbc_id=self.target['id']) + self.changed = True if self.wait: self.cdpy.sdk.wait_for_state( describe_func=self.cdpy.dw.describe_dbc, @@ -207,6 +209,7 @@ def process(self): else: dbc_id = self.cdpy.dw.create_dbc(cluster_id=self.cluster_id, name=self.name, load_demo_data=self.load_demo_data) + self.changed = True if self.wait: self.target = self.cdpy.sdk.wait_for_state( describe_func=self.cdpy.dw.describe_dbc, @@ -223,20 +226,20 @@ def process(self): def main(): module = AnsibleModule( argument_spec=CdpModule.argument_spec( - id=dict(required=False, type='str', default=None), + id=dict(type='str'), cluster_id=dict(required=True, type='str'), - name = dict(required=False, type='str', default=None), - load_demo_data=dict(required=False, type='bool', default=False), - state=dict(required=False, type='str', choices=['present', 'absent'], default='present'), - wait = dict(required=False, type='bool', default=True), - delay = dict(required=False, type='int', aliases=['polling_delay'], default=15), - timeout = dict(required=False, type='int', aliases=['polling_timeout'], default=3600) + name = dict(type='str'), + load_demo_data=dict(type='bool'), + state=dict(type='str', choices=['present', 'absent'], default='present'), + wait = dict(type='bool', default=True), + delay = dict(type='int', aliases=['polling_delay'], default=15), + timeout = dict(type='int', aliases=['polling_timeout'], default=3600) ), supports_check_mode=True ) result = DwDbc(module) - output = dict(changed=False, database_catalogs=result.database_catalogs) + output = dict(changed=result.changed, database_catalogs=result.database_catalogs) if result.debug: output.update(sdk_out=result.log_out, sdk_out_lines=result.log_lines) diff --git a/plugins/modules/dw_virtual_warehouse.py b/plugins/modules/dw_virtual_warehouse.py index 9ba59041..88dbe812 100644 --- a/plugins/modules/dw_virtual_warehouse.py +++ b/plugins/modules/dw_virtual_warehouse.py @@ -72,11 +72,12 @@ description: Configurations that are applied to every application in the service. type: dict required: False - contains: + suboptions: configBlocks: List of ConfigBlocks for the application. type: list required: False - contains: + elements: dict + suboptions: id: description: ID of the ConfigBlock. Unique within an ApplicationConfig. type: str @@ -84,21 +85,16 @@ format: description: Format of ConfigBlock. type: str - required: False + required: False content: description: Contents of a ConfigBlock. - type: obj + type: dict required: False - contains: + suboptions: keyValues: description: Key-value type configurations. - type: obj + type: dict required: False - contains: - additionalProperties: - description: Key-value type configurations. - type: str - required: False text: description: Text type configuration. type: str @@ -111,45 +107,45 @@ description: Configurations that are applied to every application in the service. type: dict required: False - contains: - configBlocks: List of ConfigBlocks for the application. - type: list - required: False - contains: - id: - description: ID of the ConfigBlock. Unique within an ApplicationConfig. - type: str - required: False - format: - description: Format of ConfigBlock. - type: str - required: False - content: - description: Contents of a ConfigBlock. - type: obj - required: False - contains: - keyValues: - description: Key-value type configurations. - type: obj - required: False - contains: - additionalProperties: - description: Key-value type configurations. - type: str - required: False - text: - description: Text type configuration. - type: str - required: False - json: - description: JSON type configuration. - type: str - required: False + suboptions: + required: False + type: dict + suboptions: + configBlocks: List of ConfigBlocks for the application. + type: list + required: False + elements: dict + suboptions: + id: + description: ID of the ConfigBlock. Unique within an ApplicationConfig. + type: str + required: False + format: + description: Format of ConfigBlock. + type: str + required: False + content: + description: Contents of a ConfigBlock. + type: dict + required: False + suboptions: + keyValues: + description: Key-value type configurations. + type: dict + required: False + text: + description: Text type configuration. + type: str + required: False + json: + description: JSON type configuration. + type: str + required: False ldap_groups: description: LDAP Groupnames to be enabled for auth. type: list required: False + elements: str enable_sso: description: Should SSO be enabled for this VW. type: bool @@ -211,6 +207,41 @@ tag-key: "tag-value" enable_sso: true ldap_groups: ['group1','group2','group3'] + +- cloudera.cloud.dw_virtual_warehouse: + cluster_id: "example-cluster-id" + name: "example-virtual-warehouse" + type: "hive" + template: "xsmall" + enable_sso: true + ldap_groups: ['group1','group2','group3'] + common_configs: "{ + 'configBlocks': [ + { + 'id': 'das-ranger-policymgr', + 'format': 'HADOOP_XML', + 'content': { + 'keyValues' : { + 'xasecure.policymgr.clientssl.truststore': '/path_to_ca_cert/cacerts' + } + } + } + ] + }" + application_configs: "{ + "das-webapp": { + "configBlocks": [ + { + "id": "hive-kerberos-config", + "format": "TEXT", + "content": { + "text": "\n[libdefaults]\n\trenew_lifetime = 7d" + } + } + ] + } + }" + # Delete Virtual Warehouse - cloudera.cloud.dw_virtual_warehouse: @@ -226,10 +257,10 @@ type: list returned: always elements: complex - contains: + suboptions: vws: type: dict - contains: + suboptions: id: description: Id of the Virtual Warehouse created. returned: always @@ -258,7 +289,7 @@ description: The CRN of the cluster creator. returned: always type: dict - contains: + suboptions: crn: type: str description: Actor CRN @@ -315,6 +346,7 @@ def __init__(self, module): # Initialize internal values self.target = None + self.changed = False # Execute logic process self.process() @@ -340,6 +372,7 @@ def process(self): 'status']) else: _ = self.cdpy.dw.delete_vw(cluster_id=self.cluster_id, vw_id=self.target['id']) + self.changed = True if self.wait: self.cdpy.sdk.wait_for_state( describe_func=self.cdpy.dw.describe_vw, @@ -383,6 +416,7 @@ def process(self): application_configs=self.application_configs, ldap_groups=self.ldap_groups, enable_sso=self.enable_sso, tags=self.tags) + self.changed = True if self.wait: self.target = self.cdpy.sdk.wait_for_state( describe_func=self.cdpy.dw.describe_vw, @@ -399,29 +433,51 @@ def process(self): def main(): module = AnsibleModule( argument_spec=CdpModule.argument_spec( - id=dict(required=False, type='str', default=None), + id=dict(type='str'), cluster_id=dict(required=True, type='str'), - dbc_id=dict(required=False, type='str', default=None), - type = dict(required=False, type='str', default=None), - name = dict(required=False, type='str', default=None), - template=dict(required=False, type='str', default=None), - autoscaling_min_nodes=dict(required=False, type='int', default=None), - autoscaling_max_nodes=dict(required=False, type='int', default=None), - common_configs=dict(required=False, type='dict', default=None), - application_configs=dict(required=False, type='dict', default=None), - ldap_groups=dict(required=False, type='list', default=None), - enable_sso=dict(required=False, type='bool', default=False), - tags=dict(required=False, type='dict', default=None), - state=dict(required=False, type='str', choices=['present', 'absent'], default='present'), - wait = dict(required=False, type='bool', default=True), - delay = dict(required=False, type='int', aliases=['polling_delay'], default=15), - timeout = dict(required=False, type='int', aliases=['polling_timeout'], default=3600) + dbc_id=dict(type='str'), + type = dict(type='str'), + name = dict(type='str'), + template=dict(type='str'), + autoscaling_min_nodes=dict(type='int'), + autoscaling_max_nodes=dict(type='int'), + common_configs=dict(type='dict', + options=dict( + configBlocks = dict( + type='list', + elements='dict', + options=dict( + dict( + id=dict(type='str'), + format=dict(type='str', choices=['HADOOP_XML', 'PROPERTIES', 'TEXT', 'JSON', 'BINARY', 'ENV', 'FLAGFILE']), + content=dict(type='dict', + options=dict( + dict( + keyValues=dict(type='dict'), + text=dict(type='str'), + json=dict(type='json') + ) + ) + ) + ) + ) + ) + ) + ), + application_configs=dict(type='dict'), + ldap_groups=dict(type='list'), + enable_sso=dict(type='bool'), + tags=dict(type='dict'), + state=dict(type='str', choices=['present', 'absent'], default='present'), + wait = dict(type='bool', default=True), + delay = dict(type='int', aliases=['polling_delay'], default=15), + timeout = dict(type='int', aliases=['polling_timeout'], default=3600) ), supports_check_mode=True ) result = DwVw(module) - output = dict(changed=False, virtual_warehouses=result.virtual_warehouses) + output = dict(changed=result.changed, virtual_warehouses=result.virtual_warehouses) if result.debug: output.update(sdk_out=result.log_out, sdk_out_lines=result.log_lines)