From 389dfe9f0930bee9e8f4fc126c29b663fb502219 Mon Sep 17 00:00:00 2001 From: Alexei Znamensky <103110+russoz@users.noreply.github.com> Date: Sun, 23 Oct 2022 22:33:07 +1300 Subject: [PATCH] manageiq_tags_info: new module (#5368) * manageiq_tags: refactor ManageIQTags class out to utils * add manageiq_tags_info module * refactor query_resource_id as a method in ManageIQ * minor adjustments * fix comments from PR * rollback register result in examples * add basic docs for return value --- .github/BOTMETA.yml | 2 + meta/runtime.yml | 2 + plugins/module_utils/manageiq.py | 136 +++++++++++++++ .../manageiq/manageiq_tags.py | 165 ++---------------- .../manageiq/manageiq_tags_info.py | 111 ++++++++++++ 5 files changed, 266 insertions(+), 150 deletions(-) create mode 100644 plugins/modules/remote_management/manageiq/manageiq_tags_info.py diff --git a/.github/BOTMETA.yml b/.github/BOTMETA.yml index 45b86fdcbe..fa9a5f355f 100644 --- a/.github/BOTMETA.yml +++ b/.github/BOTMETA.yml @@ -982,6 +982,8 @@ files: maintainers: evertmulder $modules/remote_management/manageiq/manageiq_policies_info.py: maintainers: russoz $team_manageiq + $modules/remote_management/manageiq/manageiq_tags_info.py: + maintainers: russoz $team_manageiq $modules/remote_management/manageiq/manageiq_tenant.py: maintainers: evertmulder $modules/remote_management/oneview/: diff --git a/meta/runtime.yml b/meta/runtime.yml index 85c6a5454a..8444d0b6f8 100644 --- a/meta/runtime.yml +++ b/meta/runtime.yml @@ -716,6 +716,8 @@ plugin_routing: redirect: community.general.remote_management.manageiq.manageiq_provider manageiq_tags: redirect: community.general.remote_management.manageiq.manageiq_tags + manageiq_tags_info: + redirect: community.general.remote_management.manageiq.manageiq_tags_info manageiq_tenant: redirect: community.general.remote_management.manageiq.manageiq_tenant manageiq_user: diff --git a/plugins/module_utils/manageiq.py b/plugins/module_utils/manageiq.py index 83d44c1110..cbce05b8ec 100644 --- a/plugins/module_utils/manageiq.py +++ b/plugins/module_utils/manageiq.py @@ -166,6 +166,20 @@ class ManageIQ(object): return ManageIQPolicies(manageiq, resource_type, resource_id) + def query_resource_id(self, resource_type, resource_name): + """ Query the resource name in ManageIQ. + + Returns: + the resource ID if it exists in ManageIQ, Fail otherwise. + """ + resource = self.find_collection_resource_by(resource_type, name=resource_name) + if resource: + return resource["id"] + else: + msg = "{resource_name} {resource_type} does not exist in manageiq".format( + resource_name=resource_name, resource_type=resource_type) + self.module.fail_json(msg=msg) + class ManageIQPolicies(object): """ @@ -332,3 +346,125 @@ class ManageIQPolicies(object): msg="Successfully {action}ed profiles: {profiles}".format( action=action, profiles=profiles)) + + +class ManageIQTags(object): + """ + Object to execute tags management operations of manageiq resources. + """ + + def __init__(self, manageiq, resource_type, resource_id): + self.manageiq = manageiq + + self.module = self.manageiq.module + self.api_url = self.manageiq.api_url + self.client = self.manageiq.client + + self.resource_type = resource_type + self.resource_id = resource_id + self.resource_url = '{api_url}/{resource_type}/{resource_id}'.format( + api_url=self.api_url, + resource_type=resource_type, + resource_id=resource_id) + + def full_tag_name(self, tag): + """ Returns the full tag name in manageiq + """ + return '/managed/{tag_category}/{tag_name}'.format( + tag_category=tag['category'], + tag_name=tag['name']) + + def clean_tag_object(self, tag): + """ Clean a tag object to have human readable form of: + { + full_name: STR, + name: STR, + display_name: STR, + category: STR + } + """ + full_name = tag.get('name') + categorization = tag.get('categorization', {}) + + return dict( + full_name=full_name, + name=categorization.get('name'), + display_name=categorization.get('display_name'), + category=categorization.get('category', {}).get('name')) + + def query_resource_tags(self): + """ Returns a set of the tag objects assigned to the resource + """ + url = '{resource_url}/tags?expand=resources&attributes=categorization' + try: + response = self.client.get(url.format(resource_url=self.resource_url)) + except Exception as e: + msg = "Failed to query {resource_type} tags: {error}".format( + resource_type=self.resource_type, + error=e) + self.module.fail_json(msg=msg) + + resources = response.get('resources', []) + + # clean the returned rest api tag object to look like: + # {full_name: STR, name: STR, display_name: STR, category: STR} + tags = [self.clean_tag_object(tag) for tag in resources] + + return tags + + def tags_to_update(self, tags, action): + """ Create a list of tags we need to update in ManageIQ. + + Returns: + Whether or not a change took place and a message describing the + operation executed. + """ + tags_to_post = [] + assigned_tags = self.query_resource_tags() + + # make a list of assigned full tag names strings + # e.g. ['/managed/environment/prod', ...] + assigned_tags_set = set([tag['full_name'] for tag in assigned_tags]) + + for tag in tags: + assigned = self.full_tag_name(tag) in assigned_tags_set + + if assigned and action == 'unassign': + tags_to_post.append(tag) + elif (not assigned) and action == 'assign': + tags_to_post.append(tag) + + return tags_to_post + + def assign_or_unassign_tags(self, tags, action): + """ Perform assign/unassign action + """ + # get a list of tags needed to be changed + tags_to_post = self.tags_to_update(tags, action) + if not tags_to_post: + return dict( + changed=False, + msg="Tags already {action}ed, nothing to do".format(action=action)) + + # try to assign or unassign tags to resource + url = '{resource_url}/tags'.format(resource_url=self.resource_url) + try: + response = self.client.post(url, action=action, resources=tags) + except Exception as e: + msg = "Failed to {action} tag: {error}".format( + action=action, + error=e) + self.module.fail_json(msg=msg) + + # check all entities in result to be successful + for result in response['results']: + if not result['success']: + msg = "Failed to {action}: {message}".format( + action=action, + message=result['message']) + self.module.fail_json(msg=msg) + + # successfully changed all needed tags + return dict( + changed=True, + msg="Successfully {action}ed tags".format(action=action)) diff --git a/plugins/modules/remote_management/manageiq/manageiq_tags.py b/plugins/modules/remote_management/manageiq/manageiq_tags.py index d8db5960ff..209fb5ea9d 100644 --- a/plugins/modules/remote_management/manageiq/manageiq_tags.py +++ b/plugins/modules/remote_management/manageiq/manageiq_tags.py @@ -25,17 +25,17 @@ options: state: type: str description: - - absent - tags should not exist, - - present - tags should exist, - - list - list current tags. + - C(absent) - tags should not exist. + - C(present) - tags should exist. + - C(list) - list current tags. choices: ['absent', 'present', 'list'] default: 'present' tags: type: list elements: dict description: - - tags - list of dictionaries, each includes 'name' and 'category' keys. - - required if state is present or absent. + - C(tags) - list of dictionaries, each includes C(name) and c(category) keys. + - Required if I(state) is C(present) or C(absent). resource_type: type: str description: @@ -58,7 +58,7 @@ options: ''' EXAMPLES = ''' -- name: Create new tags for a provider in ManageIQ +- name: Create new tags for a provider in ManageIQ. community.general.manageiq_tags: resource_name: 'EngLab' resource_type: 'provider' @@ -73,7 +73,7 @@ EXAMPLES = ''' password: 'smartvm' validate_certs: false -- name: Create new tags for a provider in ManageIQ +- name: Create new tags for a provider in ManageIQ. community.general.manageiq_tags: resource_id: 23000000790497 resource_type: 'provider' @@ -88,7 +88,7 @@ EXAMPLES = ''' password: 'smartvm' validate_certs: false -- name: Remove tags for a provider in ManageIQ +- name: Remove tags for a provider in ManageIQ. community.general.manageiq_tags: state: absent resource_name: 'EngLab' @@ -104,7 +104,7 @@ EXAMPLES = ''' password: 'smartvm' validate_certs: false -- name: List current tags for a provider in ManageIQ +- name: List current tags for a provider in ManageIQ. community.general.manageiq_tags: state: list resource_name: 'EngLab' @@ -120,152 +120,17 @@ RETURN = ''' ''' from ansible.module_utils.basic import AnsibleModule -from ansible_collections.community.general.plugins.module_utils.manageiq import ManageIQ, manageiq_argument_spec, manageiq_entities - - -def query_resource_id(manageiq, resource_type, resource_name): - """ Query the resource name in ManageIQ. - - Returns: - the resource id if it exists in manageiq, Fail otherwise. - """ - resource = manageiq.find_collection_resource_by(resource_type, name=resource_name) - if resource: - return resource["id"] - else: - msg = "{resource_name} {resource_type} does not exist in manageiq".format( - resource_name=resource_name, resource_type=resource_type) - manageiq.module.fail_json(msg=msg) - - -class ManageIQTags(object): - """ - Object to execute tags management operations of manageiq resources. - """ - - def __init__(self, manageiq, resource_type, resource_id): - self.manageiq = manageiq - - self.module = self.manageiq.module - self.api_url = self.manageiq.api_url - self.client = self.manageiq.client - - self.resource_type = resource_type - self.resource_id = resource_id - self.resource_url = '{api_url}/{resource_type}/{resource_id}'.format( - api_url=self.api_url, - resource_type=resource_type, - resource_id=resource_id) - - def full_tag_name(self, tag): - """ Returns the full tag name in manageiq - """ - return '/managed/{tag_category}/{tag_name}'.format( - tag_category=tag['category'], - tag_name=tag['name']) - - def clean_tag_object(self, tag): - """ Clean a tag object to have human readable form of: - { - full_name: STR, - name: STR, - display_name: STR, - category: STR - } - """ - full_name = tag.get('name') - categorization = tag.get('categorization', {}) - - return dict( - full_name=full_name, - name=categorization.get('name'), - display_name=categorization.get('display_name'), - category=categorization.get('category', {}).get('name')) - - def query_resource_tags(self): - """ Returns a set of the tag objects assigned to the resource - """ - url = '{resource_url}/tags?expand=resources&attributes=categorization' - try: - response = self.client.get(url.format(resource_url=self.resource_url)) - except Exception as e: - msg = "Failed to query {resource_type} tags: {error}".format( - resource_type=self.resource_type, - error=e) - self.module.fail_json(msg=msg) - - resources = response.get('resources', []) - - # clean the returned rest api tag object to look like: - # {full_name: STR, name: STR, display_name: STR, category: STR} - tags = [self.clean_tag_object(tag) for tag in resources] - - return tags - - def tags_to_update(self, tags, action): - """ Create a list of tags we need to update in ManageIQ. - - Returns: - Whether or not a change took place and a message describing the - operation executed. - """ - tags_to_post = [] - assigned_tags = self.query_resource_tags() - - # make a list of assigned full tag names strings - # e.g. ['/managed/environment/prod', ...] - assigned_tags_set = set([tag['full_name'] for tag in assigned_tags]) - - for tag in tags: - assigned = self.full_tag_name(tag) in assigned_tags_set - - if assigned and action == 'unassign': - tags_to_post.append(tag) - elif (not assigned) and action == 'assign': - tags_to_post.append(tag) - - return tags_to_post - - def assign_or_unassign_tags(self, tags, action): - """ Perform assign/unassign action - """ - # get a list of tags needed to be changed - tags_to_post = self.tags_to_update(tags, action) - if not tags_to_post: - return dict( - changed=False, - msg="Tags already {action}ed, nothing to do".format(action=action)) - - # try to assign or unassign tags to resource - url = '{resource_url}/tags'.format(resource_url=self.resource_url) - try: - response = self.client.post(url, action=action, resources=tags) - except Exception as e: - msg = "Failed to {action} tag: {error}".format( - action=action, - error=e) - self.module.fail_json(msg=msg) - - # check all entities in result to be successful - for result in response['results']: - if not result['success']: - msg = "Failed to {action}: {message}".format( - action=action, - message=result['message']) - self.module.fail_json(msg=msg) - - # successfully changed all needed tags - return dict( - changed=True, - msg="Successfully {action}ed tags".format(action=action)) +from ansible_collections.community.general.plugins.module_utils.manageiq import ( + ManageIQ, ManageIQTags, manageiq_argument_spec, manageiq_entities +) def main(): actions = {'present': 'assign', 'absent': 'unassign', 'list': 'list'} argument_spec = dict( tags=dict(type='list', elements='dict'), - resource_id=dict(required=False, type='int'), - resource_name=dict(required=False, type='str'), + resource_id=dict(type='int'), + resource_name=dict(type='str'), resource_type=dict(required=True, type='str', choices=list(manageiq_entities().keys())), state=dict(required=False, type='str', @@ -298,7 +163,7 @@ def main(): # query resource id, fail if resource does not exist if resource_id is None: - resource_id = query_resource_id(manageiq, resource_type, resource_name) + resource_id = manageiq.query_resource_id(resource_type, resource_name) manageiq_tags = ManageIQTags(manageiq, resource_type, resource_id) diff --git a/plugins/modules/remote_management/manageiq/manageiq_tags_info.py b/plugins/modules/remote_management/manageiq/manageiq_tags_info.py new file mode 100644 index 0000000000..0cdcd5184d --- /dev/null +++ b/plugins/modules/remote_management/manageiq/manageiq_tags_info.py @@ -0,0 +1,111 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright (c) 2017, Daniel Korn +# Copyright (c) 2017, Yaacov Zamir +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' + +module: manageiq_tags_info +version_added: 5.8.0 +short_description: Retrieve resource tags in ManageIQ +extends_documentation_fragment: +- community.general.manageiq + +author: Alexei Znamensky (@russoz) +description: + - This module supports retrieving resource tags from ManageIQ. + +options: + resource_type: + type: str + description: + - The relevant resource type in ManageIQ. + required: true + choices: ['provider', 'host', 'vm', 'blueprint', 'category', 'cluster', + 'data store', 'group', 'resource pool', 'service', 'service template', + 'template', 'tenant', 'user'] + resource_name: + type: str + description: + - The name of the resource at which tags will be controlled. + - Must be specified if I(resource_id) is not set. Both options are mutually exclusive. + resource_id: + description: + - The ID of the resource at which tags will be controlled. + - Must be specified if I(resource_name) is not set. Both options are mutually exclusive. + type: int +''' + +EXAMPLES = ''' +- name: List current tags for a provider in ManageIQ. + community.general.manageiq_tags_info: + resource_name: 'EngLab' + resource_type: 'provider' + manageiq_connection: + url: 'http://127.0.0.1:3000' + username: 'admin' + password: 'smartvm' + register: result +''' + +RETURN = ''' +tags: + description: List of tags associated with the resource. + returned: on success + type: list + elements: dict +''' + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.general.plugins.module_utils.manageiq import ( + ManageIQ, ManageIQTags, manageiq_argument_spec, manageiq_entities +) + + +def main(): + argument_spec = dict( + resource_id=dict(type='int'), + resource_name=dict(type='str'), + resource_type=dict(required=True, type='str', + choices=list(manageiq_entities().keys())), + ) + # add the manageiq connection arguments to the arguments + argument_spec.update(manageiq_argument_spec()) + + module = AnsibleModule( + argument_spec=argument_spec, + mutually_exclusive=[["resource_id", "resource_name"]], + required_one_of=[["resource_id", "resource_name"]], + supports_check_mode=True, + ) + + resource_id = module.params['resource_id'] + resource_type_key = module.params['resource_type'] + resource_name = module.params['resource_name'] + + # get the action and resource type + resource_type = manageiq_entities()[resource_type_key] + + manageiq = ManageIQ(module) + + # query resource id, fail if resource does not exist + if resource_id is None: + resource_id = manageiq.query_resource_id(resource_type, resource_name) + + manageiq_tags = ManageIQTags(manageiq, resource_type, resource_id) + + # return a list of current tags for this object + current_tags = manageiq_tags.query_resource_tags() + res_args = dict(changed=False, tags=current_tags) + + module.exit_json(**res_args) + + +if __name__ == "__main__": + main()