Repository: ansible/ansible
Pull request: Add ovirt role
##### SUMMARY
<!--- Describe the change below, including rationale and design decisions -->
Add ovirt role
<!--- HINT: Include "Fixes #nnn" if you are fixing an existing issue -->
Fixes #54199
##### ISSUE TYPE
<!--- Pick one below and delete the rest -->
- New Module Pull Request

##### COMPONENT NAME
<!--- Write the short name of the module, plugin, task or feature below -->
ovirt
##### ADDITIONAL INFORMATION
<!--- Include additional information to help people understand the change here -->
<!--- A step-by-step reproduction of the problem is helpful if there is no related issue -->

<!--- Paste verbatim command output below, e.g. before and after your change -->
```paste below

```


Problem statement:
RFE: Create ovirt_configure module
<!--- Verify first that your feature was not already discussed on GitHub -->
<!--- Complete *all* sections as described, this form is processed automatically -->

##### SUMMARY
<!--- Describe the new feature/improvement briefly below -->

Create module to allow configuration of parameters in RHV/oVirt. This module would specifically allow configuration of items in Administration => Configure. 

Initially the request was for ovirt_configure_instances to be able to create instance types through Ansible. However, there is also a need to be able to create/define roles with an Ansible playbook.

##### ISSUE TYPE
- Feature Idea

ovirt_configure module could do the following:

instance_types:
- define new instance types with all parameters. Similar to defining a VM. This component would have very similar dictionaries and lists of a VM (console, memory, etc.)

roles:
- Ability to create new custom roles and assign functions to that role.

##### COMPONENT NAME
<!--- Write the short name of the module, plugin, task or feature below, use your best guess if unsure -->

ovirt_configure

##### ADDITIONAL INFORMATION
<!--- Describe how the feature would be used, why it is needed and what it would solve -->

This module would be used to automate the creation of new instance types and custom roles among other functions. It would allow administrators to run a playbook to create a repeatable RHV configuration without the need of using the RHV-M WebUI. Currently, Ansible doesn't provide a method of performing Administration => Configuration tasks.

<!--- Paste example playbooks or commands between quotes below -->
```yaml

```

<!--- HINT: You can also paste gist.github.com links for larger files -->

Hints from issue discussion:
[11_Configuring_VM_System_Types_Lab.pdf](https://github.com/ansible/ansible/files/2994201/11_Configuring_VM_System_Types_Lab.pdf)

[12_Config_RHVM_Custom_Roles_Lab.pdf](https://github.com/ansible/ansible/files/2994205/12_Config_RHVM_Custom_Roles_Lab.pdf)

Updated with attachments describing at least two (2) problems this will solve.
+label ovirt
@machacekondra  @mwperina

Commit message: use correct syntax fo get_all_permits

Patch to review:
diff --git a/lib/ansible/modules/cloud/ovirt/ovirt_role.py b/lib/ansible/modules/cloud/ovirt/ovirt_role.py
new file mode 100644
index 00000000000000..6d298033d71059
--- /dev/null
+++ b/lib/ansible/modules/cloud/ovirt/ovirt_role.py
@@ -0,0 +1,177 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+#
+# Copyright (c) 2016 Red Hat, Inc.
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+ANSIBLE_METADATA = {'metadata_version': '1.1',
+                    'status': ['preview'],
+                    'supported_by': 'community'}
+
+DOCUMENTATION = '''
+---
+module: ovirt_role
+short_description: Module to manage roles in oVirt/RHV
+version_added: "2.8"
+author: "Martin Necas (@mnecas)"
+description:
+    - "Module to manage roles in oVirt/RHV."
+options:
+    name:
+        description:
+            - "Name of the role to manage."
+    id:
+        description:
+            - "ID of the role to manage."
+    state:
+        description:
+            - "Should the role be present/absent."
+        choices: ['present', 'absent']
+        default: present
+    mutable:
+        description:
+            - "Defines the ability to update or delete the role."
+            - "Roles with mutable set to `false` are predefined roles."
+        type: bool
+        default: true
+    administrative:
+        description:
+            - "Defines the role as administrative-only or not."
+        type: bool
+    permits:
+        description:
+            - "List of permits which role will have"
+            - "Permit 'login' is default and all roles will have it."
+            - "Dictionary can contain following value."
+            - "C(name) - Name of permit."
+extends_documentation_fragment: ovirt
+'''
+
+EXAMPLES = '''
+# Examples don't contain auth parameter for simplicity,
+# look at ovirt_auth module to see how to reuse authentication:
+
+# Create administrative role with two permits
+- ovirt_role:
+    mutable: true
+    name: role
+    administrative: true
+    permits:
+        - manipulate_permissions
+        - create_instance
+'''
+
+RETURN = '''
+ovirt_role:
+    description: "List of dictionaries describing the Roles. Role attributes are mapped to dictionary keys,
+                  all Roles attributes can be found at following url: http://ovirt.github.io/ovirt-engine-api-model/master/#types/role."
+    returned: On success.
+    type: list
+'''
+
+from ansible.module_utils.ovirt import (
+    BaseModule,
+    check_sdk,
+    convert_to_bytes,
+    create_connection,
+    equal,
+    get_dict_of_struct,
+    get_link_name,
+    get_id_by_name,
+    ovirt_full_argument_spec,
+    search_by_attributes,
+    search_by_name,
+)
+from ansible.module_utils.basic import AnsibleModule
+import traceback
+
+try:
+    import ovirtsdk4.types as otypes
+except ImportError:
+    pass
+
+
+class RoleModule(BaseModule):
+    def build_entity(self):
+        all_permits = self.get_all_permits()
+        return otypes.Role(
+            id=self.param('id'),
+            name=self.param('name'),
+            mutable=self.param('mutable') if self.param('mutable') else None,
+            administrative=self.param('administrative') if self.param(
+                'administrative') else None,
+            permits=[
+                otypes.Permit(id=all_permits.get(new_permit)) for new_permit in self.param('permits')
+            ]
+        )
+
+    def get_all_permits(self):
+        return dict((permit.name, permit.id) for permit in self._connection.system_service().cluster_levels_service().level_service('4.3').get().permits)
+
+    def update_check(self, entity):
+        def check_permits():
+            if self.param('permits'):
+                permits_service = self._service.service(entity.id).permits_service()
+                current = [er.name for er in permits_service.list()]
+                passed = [pr for pr in self.param('permits')]
+                if not sorted(current) == sorted(passed):
+                    # remove all
+                    for permit in permits_service.list():
+                        permits_service.permit_service(permit.id).remove()
+                    # add passed permits
+                    all_permits = self.get_all_permits()
+                    for new_permit in passed:
+                        permits_service.add(otypes.Permit(id=all_permits.get(new_permit)))
+                    return False
+            return True
+
+        return (
+            check_permits() and
+            equal(self.param('mutable'), entity.mutable) and
+            equal(self.param('administrative'), entity.administrative)
+        )
+
+
+def main():
+    argument_spec = ovirt_full_argument_spec(
+        state=dict(
+            choices=['present', 'absent'],
+            default='present',
+        ),
+        id=dict(default=None),
+        name=dict(default=None),
+        mutable=dict(default=True, type='bool'),
+        administrative=dict(default=False, type='bool'),
+        permits=dict(type='list', default=[]),
+    )
+    module = AnsibleModule(
+        argument_spec=argument_spec,
+        required_one_of=[['id', 'name']],
+    )
+
+    check_sdk(module)
+
+    try:
+        auth = module.params.pop('auth')
+        connection = create_connection(auth)
+        roles_service = connection.system_service().roles_service()
+        roles_module = RoleModule(
+            connection=connection,
+            module=module,
+            service=roles_service,
+        )
+        state = module.params['state']
+        if state == 'present':
+            module.params.get('permits').append('login')
+            ret = roles_module.create()
+        elif state == 'absent':
+            ret = roles_module.remove()
+        module.exit_json(**ret)
+    except Exception as e:
+        module.fail_json(msg=str(e), exception=traceback.format_exc())
+    finally:
+        connection.close(logout=auth.get('token') is None)
+
+
+if __name__ == "__main__":
+    main()


Code review comments:
diff_hunk: @@ -0,0 +1,177 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+#
+# Copyright (c) 2016 Red Hat, Inc.
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+ANSIBLE_METADATA = {'metadata_version': '1.1',
+                    'status': ['preview'],
+                    'supported_by': 'community'}
+
+DOCUMENTATION = '''
+---
+module: ovirt_role
+short_description: Module to manage roles in oVirt/RHV
+version_added: "2.8"
+author: "Martin Necas (@mnecas)"
+description:
+    - "Module to manage roles in oVirt/RHV."
+options:
+    name:
+        description:
+            - "Name of the role to manage."
+    id:
+        description:
+            - "ID of the role to manage."
+    state:
+        description:
+            - "Should the role be present/absent."
+        choices: ['present', 'absent']
+        default: present
+    mutable:
path: lib/ansible/modules/cloud/ovirt/ovirt_role.py
text: @user1:
I think you can't create imutable role, and all roles created by users are mutable, so this parameter is not needed.

diff_hunk: @@ -0,0 +1,177 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+#
+# Copyright (c) 2016 Red Hat, Inc.
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+ANSIBLE_METADATA = {'metadata_version': '1.1',
+                    'status': ['preview'],
+                    'supported_by': 'community'}
+
+DOCUMENTATION = '''
+---
+module: ovirt_role
+short_description: Module to manage roles in oVirt/RHV
+version_added: "2.8"
+author: "Martin Necas (@mnecas)"
+description:
+    - "Module to manage roles in oVirt/RHV."
+options:
+    name:
+        description:
+            - "Name of the role to manage."
+    id:
+        description:
+            - "ID of the role to manage."
+    state:
+        description:
+            - "Should the role be present/absent."
+        choices: ['present', 'absent']
+        default: present
+    mutable:
+        description:
+            - "Defines the ability to update or delete the role."
+            - "Roles with mutable set to `false` are predefined roles."
+        type: bool
+        default: true
+    administrative:
+        description:
+            - "Defines the role as administrative-only or not."
+        type: bool
+    permits:
+        description:
+            - "List of permits which role will have"
+            - "Permit 'login' is default and all roles will have it."
+            - "Dictionary can contain following value."
path: lib/ansible/modules/cloud/ovirt/ovirt_role.py
text: @user1:
It's not dicitinonary. It's list of permit names.

diff_hunk: @@ -0,0 +1,177 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+#
+# Copyright (c) 2016 Red Hat, Inc.
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+ANSIBLE_METADATA = {'metadata_version': '1.1',
+                    'status': ['preview'],
+                    'supported_by': 'community'}
+
+DOCUMENTATION = '''
+---
+module: ovirt_role
+short_description: Module to manage roles in oVirt/RHV
+version_added: "2.8"
+author: "Martin Necas (@mnecas)"
+description:
+    - "Module to manage roles in oVirt/RHV."
+options:
+    name:
+        description:
+            - "Name of the role to manage."
+    id:
+        description:
+            - "ID of the role to manage."
+    state:
+        description:
+            - "Should the role be present/absent."
+        choices: ['present', 'absent']
+        default: present
+    mutable:
+        description:
+            - "Defines the ability to update or delete the role."
+            - "Roles with mutable set to `false` are predefined roles."
+        type: bool
+        default: true
+    administrative:
+        description:
+            - "Defines the role as administrative-only or not."
+        type: bool
+    permits:
+        description:
+            - "List of permits which role will have"
+            - "Permit 'login' is default and all roles will have it."
+            - "Dictionary can contain following value."
+            - "C(name) - Name of permit."
+extends_documentation_fragment: ovirt
+'''
+
+EXAMPLES = '''
+# Examples don't contain auth parameter for simplicity,
+# look at ovirt_auth module to see how to reuse authentication:
+
+# Create administrative role with two permits
+- ovirt_role:
+    mutable: true
+    name: role
+    administrative: true
+    permits:
+        - manipulate_permissions
+        - create_instance
+'''
+
+RETURN = '''
+ovirt_role:
+    description: "List of dictionaries describing the Roles. Role attributes are mapped to dictionary keys,
+                  all Roles attributes can be found at following url: http://ovirt.github.io/ovirt-engine-api-model/master/#types/role."
+    returned: On success.
+    type: list
+'''
+
+from ansible.module_utils.ovirt import (
+    BaseModule,
+    check_sdk,
+    convert_to_bytes,
+    create_connection,
+    equal,
+    get_dict_of_struct,
+    get_link_name,
+    get_id_by_name,
+    ovirt_full_argument_spec,
+    search_by_attributes,
+    search_by_name,
+)
+from ansible.module_utils.basic import AnsibleModule
+import traceback
+
+try:
+    import ovirtsdk4.types as otypes
+except ImportError:
+    pass
+
+
+class RoleModule(BaseModule):
+    def build_entity(self):
+        all_permits = self.get_all_permits()
+        return otypes.Role(
+            id=self.param('id'),
+            name=self.param('name'),
+            mutable=self.param('mutable') if self.param('mutable') else None,
+            administrative=self.param('administrative') if self.param(
+                'administrative') else None,
+            permits=[
+                otypes.Permit(id=all_permits.get(new_permit)) for new_permit in self.param('permits')
+            ]
+        )
+
+    def get_all_permits(self):
+        return dict((permit.name, permit.id) for permit in self._connection.system_service().cluster_levels_service().level_service('4.3').get().permits)
+
+    def update_check(self, entity):
+        def check_permits():
+            if self.param('permits'):
+                permits_service = self._service.service(entity.id).permits_service()
+                current = [er.name for er in permits_service.list()]
+                passed = [pr for pr in self.param('permits')]
+                if not sorted(current) == sorted(passed):
+                    # remove all
+                    for permit in permits_service.list():
+                        permits_service.permit_service(permit.id).remove()
+                    # add passed permits
+                    all_permits = self.get_all_permits()
+                    for new_permit in passed:
+                        permits_service.add(otypes.Permit(id=all_permits.get(new_permit)))
+                    return False
+            return True
+
+        return (
+            check_permits() and
+            equal(self.param('mutable'), entity.mutable) and
+            equal(self.param('administrative'), entity.administrative)
+        )
+
+
+def main():
+    argument_spec = ovirt_full_argument_spec(
+        state=dict(
+            choices=['present', 'absent'],
+            default='present',
+        ),
+        id=dict(default=None),
+        name=dict(default=None),
+        mutable=dict(default=True, type='bool'),
+        administrative=dict(default=False, type='bool'),
+        permits=dict(type='list', default=[]),
+    )
+    module = AnsibleModule(
+        argument_spec=argument_spec,
+        required_one_of=[['id', 'name']],
+    )
+
+    check_sdk(module)
+
+    try:
+        auth = module.params.pop('auth')
+        connection = create_connection(auth)
+        roles_service = connection.system_service().roles_service()
+        roles_module = RoleModule(
+            connection=connection,
+            module=module,
+            service=roles_service,
+        )
+        state = module.params['state']
+        if state == 'present':
+            module.params.get('permits').append('login')
path: lib/ansible/modules/cloud/ovirt/ovirt_role.py
text: @user1:
I just wonder what will happen if user specify login and you append login, maybe it will fail on engine side? Can you please use set and cast it to list?

diff_hunk: @@ -0,0 +1,177 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+#
+# Copyright (c) 2016 Red Hat, Inc.
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+ANSIBLE_METADATA = {'metadata_version': '1.1',
+                    'status': ['preview'],
+                    'supported_by': 'community'}
+
+DOCUMENTATION = '''
+---
+module: ovirt_role
+short_description: Module to manage roles in oVirt/RHV
+version_added: "2.8"
+author: "Martin Necas (@mnecas)"
+description:
+    - "Module to manage roles in oVirt/RHV."
+options:
+    name:
+        description:
+            - "Name of the role to manage."
+    id:
+        description:
+            - "ID of the role to manage."
+    state:
+        description:
+            - "Should the role be present/absent."
+        choices: ['present', 'absent']
+        default: present
+    mutable:
+        description:
+            - "Defines the ability to update or delete the role."
+            - "Roles with mutable set to `false` are predefined roles."
+        type: bool
+        default: true
+    administrative:
+        description:
+            - "Defines the role as administrative-only or not."
+        type: bool
+    permits:
+        description:
+            - "List of permits which role will have"
+            - "Permit 'login' is default and all roles will have it."
+            - "Dictionary can contain following value."
+            - "C(name) - Name of permit."
path: lib/ansible/modules/cloud/ovirt/ovirt_role.py
text: @user1:
You miss description parameter of the role.