Skip to content

Commit b7b4167

Browse files
committed
De-couple backend logic from module logic
1 parent 09c21d5 commit b7b4167

File tree

2 files changed

+134
-10
lines changed

2 files changed

+134
-10
lines changed
+124
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
# -*- coding: utf-8 -*-
2+
3+
# Copyright: (c) 2015, Joseph Callen <jcallen () csc.com>
4+
# Copyright: (c) 2018, Ansible Project
5+
# Copyright: (c) 2018, James E. King III (@jeking3) <jking@apache.org>
6+
# Copyright: (c) 2025, Mario Lenz (@mariolenz) <m@riolenz.de>
7+
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
8+
# SPDX-License-Identifier: GPL-3.0-or-later
9+
10+
__metaclass__ = type
11+
12+
import atexit
13+
import requests
14+
import ssl
15+
import traceback
16+
17+
from ansible.module_utils.basic import missing_required_lib
18+
from pyVim import connect
19+
from pyVmomi import vim, vmodl
20+
21+
REQUESTS_IMP_ERR = None
22+
try:
23+
# requests is required for exception handling of the ConnectionError
24+
import requests
25+
except ImportError:
26+
REQUESTS_IMP_ERR = traceback.format_exc()
27+
28+
PYVMOMI_IMP_ERR = None
29+
try:
30+
from pyVim import connect
31+
from pyVmomi import vim, vmodl, VmomiSupport
32+
except ImportError:
33+
PYVMOMI_IMP_ERR = traceback.format_exc()
34+
35+
36+
class MissingLibError(Exception):
37+
def __init__(self, library, exception, url=None):
38+
self.exception = exception
39+
self.library = library
40+
self.url = url
41+
super().__init__(missing_required_lib(self.library, url=self.url))
42+
43+
44+
class ApiAccessError(Exception):
45+
def __init__(self, *args, **kwargs):
46+
super(ApiAccessError, self).__init__(*args, **kwargs)
47+
48+
49+
class PyvmomiClient(object):
50+
def __init__(self, hostname, username, password, port=443, validate_certs=True, http_proxy_host=None, http_proxy_port=None):
51+
if REQUESTS_IMP_ERR:
52+
raise MissingLibError('requests', REQUESTS_IMP_ERR)
53+
54+
if PYVMOMI_IMP_ERR:
55+
raise MissingLibError('pyvmomi', PYVMOMI_IMP_ERR)
56+
57+
self.si, self.content = self._connect_to_api(hostname, username, password, port, validate_certs, http_proxy_host, http_proxy_port)
58+
59+
def _connect_to_api(self, hostname, username, password, port, validate_certs, http_proxy_host, http_proxy_port):
60+
if not hostname:
61+
raise ApiAccessError("Hostname parameter is missing.")
62+
63+
if not username:
64+
raise ApiAccessError("Username parameter is missing.")
65+
66+
if not password:
67+
raise ApiAccessError("Password parameter is missing.")
68+
69+
if validate_certs and not hasattr(ssl, 'SSLContext'):
70+
raise ApiAccessError('pyVim does not support changing verification mode with python < 2.7.9. Either update '
71+
'python or use validate_certs=false.')
72+
elif validate_certs:
73+
ssl_context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
74+
ssl_context.verify_mode = ssl.CERT_REQUIRED
75+
ssl_context.check_hostname = True
76+
ssl_context.load_default_certs()
77+
elif hasattr(ssl, 'SSLContext'):
78+
ssl_context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
79+
ssl_context.verify_mode = ssl.CERT_NONE
80+
ssl_context.check_hostname = False
81+
82+
service_instance = None
83+
84+
connect_args = dict(
85+
host=hostname,
86+
port=port,
87+
)
88+
if ssl_context:
89+
connect_args.update(sslContext=ssl_context)
90+
91+
msg_suffix = ''
92+
try:
93+
if http_proxy_host:
94+
msg_suffix = " [proxy: %s:%d]" % (http_proxy_host, http_proxy_port)
95+
connect_args.update(httpProxyHost=http_proxy_host, httpProxyPort=http_proxy_port)
96+
smart_stub = connect.SmartStubAdapter(**connect_args)
97+
session_stub = connect.VimSessionOrientedStub(smart_stub, connect.VimSessionOrientedStub.makeUserLoginMethod(username, password))
98+
service_instance = vim.ServiceInstance('ServiceInstance', session_stub)
99+
else:
100+
connect_args.update(user=username, pwd=password)
101+
service_instance = connect.SmartConnect(**connect_args)
102+
except vim.fault.InvalidLogin as invalid_login:
103+
msg = "Unable to log on to vCenter or ESXi API at %s:%s " % (hostname, port)
104+
raise ApiAccessError("%s as %s: %s" % (msg, username, invalid_login.msg) + msg_suffix)
105+
except vim.fault.NoPermission as no_permission:
106+
raise ApiAccessError("User %s does not have required permission"
107+
" to log on to vCenter or ESXi API at %s:%s : %s" % (username, hostname, port, no_permission.msg))
108+
except (requests.ConnectionError, ssl.SSLError) as generic_req_exc:
109+
raise ApiAccessError("Unable to connect to vCenter or ESXi API at %s on TCP/%s: %s" % (hostname, port, generic_req_exc))
110+
except vmodl.fault.InvalidRequest as invalid_request:
111+
# Request is malformed
112+
msg = "Failed to get a response from server %s:%s " % (hostname, port)
113+
raise ApiAccessError("%s as request is malformed: %s" % (msg, invalid_request.msg) + msg_suffix)
114+
except Exception as generic_exc:
115+
msg = "Unknown error while connecting to vCenter or ESXi API at %s:%s" % (hostname, port) + msg_suffix
116+
raise ApiAccessError("%s : %s" % (msg, generic_exc))
117+
118+
if service_instance is None:
119+
msg = "Unknown error while connecting to vCenter or ESXi API at %s:%s" % (hostname, port)
120+
raise ApiAccessError(msg + msg_suffix)
121+
122+
atexit.register(connect.Disconnect, service_instance)
123+
124+
return service_instance, service_instance.RetrieveContent()

plugins/module_utils/vmware.py

+10-10
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
77
# SPDX-License-Identifier: GPL-3.0-or-later
88

9-
from __future__ import absolute_import, division, print_function
109
__metaclass__ = type
1110

1211
import atexit
@@ -22,6 +21,7 @@
2221
import datetime
2322
from collections import OrderedDict
2423
from ansible.module_utils.compat.version import StrictVersion
24+
from ansible_collections.community.vmware.plugins.module_utils.clients._vmware import PyvmomiClient, ApiAccessError
2525
from random import randint
2626

2727

@@ -54,11 +54,6 @@ def __init__(self, *args, **kwargs):
5454
super(TaskError, self).__init__(*args, **kwargs)
5555

5656

57-
class ApiAccessError(Exception):
58-
def __init__(self, *args, **kwargs):
59-
super(ApiAccessError, self).__init__(*args, **kwargs)
60-
61-
6257
def check_answer_question_status(vm):
6358
"""Check whether locked a virtual machine.
6459
@@ -1067,11 +1062,8 @@ def quote_obj_name(object_name=None):
10671062
return object_name
10681063

10691064

1070-
class PyVmomi(object):
1065+
class PyVmomi(PyvmomiClient):
10711066
def __init__(self, module):
1072-
"""
1073-
Constructor
1074-
"""
10751067
if not HAS_REQUESTS:
10761068
module.fail_json(msg=missing_required_lib('requests'),
10771069
exception=REQUESTS_IMP_ERR)
@@ -1080,6 +1072,14 @@ def __init__(self, module):
10801072
module.fail_json(msg=missing_required_lib('PyVmomi'),
10811073
exception=PYVMOMI_IMP_ERR)
10821074

1075+
try:
1076+
super().__init__(module.params['hostname'], module.params['username'],
1077+
module.params['password'], module.params['port'],
1078+
module.params['validate_certs'],
1079+
module.params['proxy_host'],
1080+
module.params['proxy_port'])
1081+
except ApiAccessError as aae:
1082+
module.fail_json(msg=str(aae))
10831083
self.module = module
10841084
self.params = module.params
10851085
self.current_vm_obj = None

0 commit comments

Comments
 (0)