Repository: ansible/ansible

Issue:
vcenter_license: New module
<!---
Verify first that your issue/request is not already reported on GitHub.
Also test if the latest release, and master branch are affected too.
-->

##### ISSUE TYPE
<!--- Pick one below and delete the rest: -->
 - Feature Idea

##### COMPONENT NAME
<!--- Name of the module/plugin/task/feature -->
vcenter_license

##### ANSIBLE VERSION
<!--- Paste verbatim output from “ansible --version” between quotes below -->
v2.4

##### CONFIGURATION
<!---
Mention any settings you have changed/added/removed in ansible.cfg
(or using the ANSIBLE_* environment variables).
-->

##### SUMMARY
<!--- Explain the problem briefly -->
Managing vCenter license keys is currently not possible, but essential when creating infrastructure from scratch. I plan to create a module that can add and remove license keys.


Discussion:

Resolving patch:
diff --git a/lib/ansible/modules/cloud/vmware/vcenter_license.py b/lib/ansible/modules/cloud/vmware/vcenter_license.py
new file mode 100644
--- /dev/null
+++ b/lib/ansible/modules/cloud/vmware/vcenter_license.py
@@ -0,0 +1,208 @@
+#!/usr/bin/python
+
+# Copyright 2017, Dag Wieers <dag@wieers.com>
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible.  If not, see <http://www.gnu.org/licenses/>.
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+ANSIBLE_METADATA = {'metadata_version': '1.0',
+                    'status': ['preview'],
+                    'supported_by': 'community'}
+
+DOCUMENTATION = r'''
+module: vcenter_license
+short_description: Manage VMware vCenter license keys
+description:
+- Add and delete vCenter license keys.
+version_added: '2.4'
+author: Dag Wieers (@dagwieers)
+requirements:
+- pyVmomi
+options:
+  hostname:
+    description:
+    - The hostname or IP address of the vSphere vCenter.
+    required: yes
+  username:
+    description:
+    - The username to log into the vSphere vCenter.
+    required: yes
+    aliases: [admin, user]
+  password:
+    description:
+    - The password to log into to the vSphere vCenter.
+    required: yes
+    aliases: [pass, pwd]
+  labels:
+    description:
+    - The optional labels of the license key to manage in vSphere vCenter.
+    - This is dictionary with key/value pair.
+  license:
+    description:
+    - The license key to manage in vSphere vCenter.
+    required: yes
+  state:
+    description:
+    -  Whether to add (C(present)) or remove (C(absent)) the license key.
+    choices: [absent, present]
+    default: present
+notes:
+- This module will also auto-assign the current vCenter to the license key
+  if the product matches the license key, and vCenter us currently assigned
+  an evaluation license only.
+- The evaluation license (00000-00000-00000-00000-00000) is not listed
+  when unused.
+'''
+
+EXAMPLES = r'''
+- name: Add a new vCenter license
+  vcenter_license:
+    hostname: '{{ vcenter_hostname }}'
+    username: '{{ vcenter_username }}'
+    password: '{{ vcenter_password }}'
+    license: f600d-21ae3-5592b-249e0-cc341
+    state: present
+  delegate_to: localhost
+
+- name: Remove an (unused) vCenter license
+  vmware_license:
+    hostname: '{{ vcenter_hostname }}'
+    username: '{{ vcenter_username }}'
+    password: '{{ vcenter_password }}'
+    license: f600d-21ae3-5592b-249e0-cc341
+    state: absent
+  delegate_to: localhost
+'''
+
+RETURN = r'''
+licenses:
+    description: list of license keys after module executed
+    returned: always
+    type: list
+    sample:
+    - f600d-21ae3-5592b-249e0-cc341
+    - 143cc-0e942-b2955-3ea12-d006f
+'''
+
+try:
+    from pyVmomi import vim, vmodl
+    HAS_PYVMOMI = True
+except ImportError:
+    HAS_PYVMOMI = False
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible.module_utils.vmware import connect_to_api, vmware_argument_spec
+
+
+def find_key(licenses, license):
+    for item in licenses:
+        if item.licenseKey == license:
+            return item
+    return None
+
+
+def list_keys(licenses):
+    keys = []
+    for item in licenses:
+        # Filter out evaluation license key
+        if item.used is None:
+            continue
+        keys.append(item.licenseKey)
+    return keys
+
+
+def main():
+    argument_spec = vmware_argument_spec()
+    argument_spec.update(dict(
+        labels=dict(type='dict', default=dict(source='ansible')),
+        license=dict(type='str', required=True),
+        state=dict(type='str', default='present', choices=['absent', 'present']),
+    ))
+
+    module = AnsibleModule(
+        argument_spec=argument_spec,
+        supports_check_mode=True,
+    )
+
+    license = module.params['license']
+    state = module.params['state']
+
+    # FIXME: This does not seem to work on vCenter v6.0
+    labels = []
+    for k in module.params['labels']:
+        kv = vim.KeyValue()
+        kv.key = k
+        kv.value = module.params['labels'][k]
+        labels.append(kv)
+
+    result = dict(
+        changed=False,
+        diff=dict(),
+    )
+
+    if not HAS_PYVMOMI:
+        module.fail_json(msg='pyvmomi is required for this module')
+
+    content = connect_to_api(module)
+    lm = content.licenseManager
+
+    result['licenses'] = list_keys(lm.licenses)
+    if module._diff:
+        result['diff']['before'] = '\n'.join(result['licenses']) + '\n'
+
+    if state == 'present' and license not in result['licenses']:
+
+        result['changed'] = True
+        if module.check_mode:
+            result['licenses'].append(license)
+        else:
+            lm.AddLicense(license, labels)
+
+            # Automatically assign to current vCenter, if needed
+            key = find_key(lm.licenses, license)
+            if content.about.name in key.name:
+                try:
+                    lam = lm.licenseAssignmentManager
+                    lam.UpdateAssignedLicense(entity=content.about.instanceUuid, licenseKey=license)
+                except:
+                    module.warn('Could not assign "%s" (%s) to vCenter.' % (license, key.name))
+
+            result['licenses'] = list_keys(lm.licenses)
+        if module._diff:
+            result['diff']['after'] = '\n'.join(result['licenses']) + '\n'
+
+    elif state == 'absent' and license in result['licenses']:
+
+        # Check if key is in use
+        key = find_key(lm.licenses, license)
+        if key.used > 0:
+            module.fail_json(msg='Cannot remove key "%s", still in use %s time(s).' % (license, key.used))
+
+        result['changed'] = True
+        if module.check_mode:
+            result['licenses'].remove(license)
+        else:
+            lm.RemoveLicense(license)
+            result['licenses'] = list_keys(lm.licenses)
+        if module._diff:
+            result['diff']['after'] = '\n'.join(result['licenses']) + '\n'
+
+    module.exit_json(**result)
+
+if __name__ == '__main__':
+    main()


Test patch:
diff --git a/test/integration/targets/vcenter_license/aliases b/test/integration/targets/vcenter_license/aliases
new file mode 100644
--- /dev/null
+++ b/test/integration/targets/vcenter_license/aliases
@@ -0,0 +1,2 @@
+posix/ci/cloud/vcenter
+cloud/vcenter
diff --git a/test/integration/targets/vcenter_license/tasks/main.yml b/test/integration/targets/vcenter_license/tasks/main.yml
new file mode 100644
--- /dev/null
+++ b/test/integration/targets/vcenter_license/tasks/main.yml
@@ -0,0 +1,76 @@
+# Test code for the vcenter license module.
+# (c) 2017, Dag Wieers <dag@wieers.com>
+
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible.  If not, see <http://www.gnu.org/licenses/>.
+
+- name: Make sure pyvmomi is installed
+  pip:
+    name: pyvmomi
+    state: latest
+
+- name: Store the vCenter container IP
+  set_fact:
+    vcenter_host: "{{ lookup('env', 'vcenter_host') }}"
+
+- debug:
+    var: vcenter_host
+
+- name: Wait for vcsim controller to come online {{ vcenter_host }}
+  wait_for:
+    host: "{{ vcenter_host }}"
+    port: 5000
+    state: started
+
+- name: Kill vcsim
+  uri:
+    url: http://{{ vcenter_host }}:5000/killall
+
+- name: Start vcsim
+  uri:
+    url: http://{{ vcenter_host }}:5000/spawn?cluster=2
+  register: vcsim_instance
+
+- debug:
+    var: vcsim_instance
+
+# FIXME: ServerFaultCode: LicenseManager:LicenseManager does not implement: AddLicense
+#- name: Add a vCenter evaluation license
+#  vcenter_license:
+#    hostname: '{{ vcenter_host }}'
+#    username: '{{ vcsim_instance["json"]["username"] }}'
+#    password: '{{ vcsim_instance["json"]["password"] }}'
+#    validate_certs: no
+#    license: 00000-00000-00000-00000-00000
+#    state: present
+
+#- name: Remove an (unused) vCenter evaluation license
+#  vcenter_license:
+#    hostname: '{{ vcenter_host }}'
+#    username: '{{ vcsim_instance["json"]["username"] }}'
+#    password: '{{ vcsim_instance["json"]["password"] }}'
+#    validate_certs: no
+#    license: 00000-00000-00000-00000-00000
+#    state: absent
+
+#- name: Add an invalid vCenter license
+#  vcenter_license:
+#    hostname: '{{ vcenter_host }}'
+#    username: '{{ vcsim_instance["json"]["username"] }}'
+#    password: '{{ vcsim_instance["json"]["password"] }}'
+#    validate_certs: no
+#    license: 00000-00000-00000-00000-00001
+#    state: present
+#    ignore_errors: yes