Reorganize k8s common (#34212)

* Reorganize k8s common

* Separates AnsibleMixin

* Fix lint issues
This commit is contained in:
Chris Houseknecht 2017-12-23 16:41:38 -05:00 committed by GitHub
parent 8ddb84a155
commit 9b4a1ed475
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 386 additions and 375 deletions

View file

View file

@ -0,0 +1,368 @@
#
# Copyright 2018 Red Hat | Ansible
#
# 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
import os
import re
import copy
from ansible.module_utils.six import iteritems
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.k8s.helper import\
AnsibleMixin,\
HAS_STRING_UTILS,\
COMMON_ARG_SPEC,\
AUTH_ARG_SPEC,\
OPENSHIFT_ARG_SPEC
try:
from openshift.helper.kubernetes import KubernetesObjectHelper
from openshift.helper.openshift import OpenShiftObjectHelper
from openshift.helper.exceptions import KubernetesException
HAS_K8S_MODULE_HELPER = True
except ImportError as exc:
class KubernetesObjectHelper(object):
pass
class OpenShiftObjectHelper(object):
pass
HAS_K8S_MODULE_HELPER = False
try:
import yaml
HAS_YAML = True
except ImportError:
HAS_YAML = False
class KubernetesAnsibleModuleHelper(AnsibleMixin, KubernetesObjectHelper):
pass
class KubernetesAnsibleModule(AnsibleModule):
def __init__(self):
if not HAS_K8S_MODULE_HELPER:
raise Exception(
"This module requires the OpenShift Python client. Try `pip install openshift`"
)
if not HAS_YAML:
raise Exception(
"This module requires PyYAML. Try `pip install PyYAML`"
)
if not HAS_STRING_UTILS:
raise Exception(
"This module requires Python string utils. Try `pip install python-string-utils`"
)
mutually_exclusive = [
('resource_definition', 'src'),
]
AnsibleModule.__init__(self,
argument_spec=self._argspec,
supports_check_mode=True,
mutually_exclusive=mutually_exclusive)
self.kind = self.params.pop('kind')
self.api_version = self.params.pop('api_version')
self.resource_definition = self.params.pop('resource_definition')
self.src = self.params.pop('src')
if self.src:
self.resource_definition = self.load_resource_definition(self.src)
if self.resource_definition:
self.api_version = self.resource_definition.get('apiVersion')
self.kind = self.resource_definition.get('kind')
self.api_version = self.api_version.lower()
self.kind = self._to_snake(self.kind)
if not self.api_version:
self.fail_json(
msg=("Error: no api_version specified. Use the api_version parameter, or provide it as part of a ",
"resource_definition.")
)
if not self.kind:
self.fail_json(
msg="Error: no kind specified. Use the kind parameter, or provide it as part of a resource_definition"
)
self.helper = self._get_helper(self.api_version, self.kind)
@property
def _argspec(self):
argspec = copy.deepcopy(COMMON_ARG_SPEC)
argspec.update(copy.deepcopy(AUTH_ARG_SPEC))
return argspec
def _get_helper(self, api_version, kind):
try:
helper = KubernetesAnsibleModuleHelper(api_version=api_version, kind=kind, debug=False)
helper.get_model(api_version, kind)
return helper
except KubernetesException as exc:
self.fail_json(msg="Error initializing module helper {0}".format(exc.message))
def execute_module(self):
if self.resource_definition:
resource_params = self.resource_to_parameters(self.resource_definition)
self.params.update(resource_params)
self._authenticate()
state = self.params.pop('state', None)
force = self.params.pop('force', False)
name = self.params.get('name')
namespace = self.params.get('namespace')
existing = None
self._remove_aliases()
return_attributes = dict(changed=False, result=dict())
if self._diff:
return_attributes['request'] = self.helper.request_body_from_params(self.params)
if self.helper.base_model_name_snake.endswith('list'):
k8s_obj = self._read(name, namespace)
return_attributes['result'] = k8s_obj.to_dict()
self.exit_json(**return_attributes)
try:
existing = self.helper.get_object(name, namespace)
except KubernetesException as exc:
self.fail_json(msg='Failed to retrieve requested object: {0}'.format(exc.message),
error=exc.value.get('status'))
if state == 'absent':
if not existing:
# The object already does not exist
self.exit_json(**return_attributes)
else:
# Delete the object
if not self.check_mode:
try:
self.helper.delete_object(name, namespace)
except KubernetesException as exc:
self.fail_json(msg="Failed to delete object: {0}".format(exc.message),
error=exc.value.get('status'))
return_attributes['changed'] = True
self.exit_json(**return_attributes)
else:
if not existing:
k8s_obj = self._create(namespace)
return_attributes['result'] = k8s_obj.to_dict()
return_attributes['changed'] = True
self.exit_json(**return_attributes)
if existing and force:
k8s_obj = None
request_body = self.helper.request_body_from_params(self.params)
if not self.check_mode:
try:
k8s_obj = self.helper.replace_object(name, namespace, body=request_body)
except KubernetesException as exc:
self.fail_json(msg="Failed to replace object: {0}".format(exc.message),
error=exc.value.get('status'))
return_attributes['result'] = k8s_obj.to_dict()
return_attributes['changed'] = True
self.exit_json(**return_attributes)
# Check if existing object should be patched
k8s_obj = copy.deepcopy(existing)
try:
self.helper.object_from_params(self.params, obj=k8s_obj)
except KubernetesException as exc:
self.fail_json(msg="Failed to patch object: {0}".format(exc.message))
match, diff = self.helper.objects_match(existing, k8s_obj)
if match:
return_attributes['result'] = existing.to_dict()
self.exit_json(**return_attributes)
elif self._diff:
return_attributes['differences'] = diff
# Differences exist between the existing obj and requested params
if not self.check_mode:
try:
k8s_obj = self.helper.patch_object(name, namespace, k8s_obj)
except KubernetesException as exc:
self.fail_json(msg="Failed to patch object: {0}".format(exc.message))
return_attributes['result'] = k8s_obj.to_dict()
return_attributes['changed'] = True
self.exit_json(**return_attributes)
def _authenticate(self):
try:
auth_options = {}
auth_args = ('host', 'api_key', 'kubeconfig', 'context', 'username', 'password',
'cert_file', 'key_file', 'ssl_ca_cert', 'verify_ssl')
for key, value in iteritems(self.params):
if key in auth_args and value is not None:
auth_options[key] = value
self.helper.set_client_config(**auth_options)
except KubernetesException as e:
self.fail_json(msg='Error loading config', error=str(e))
def _remove_aliases(self):
"""
The helper doesn't know what to do with aliased keys
"""
for k, v in iteritems(self._argspec):
if 'aliases' in v:
for alias in v['aliases']:
if alias in self.params:
self.params.pop(alias)
def _create(self, namespace):
request_body = None
k8s_obj = None
try:
request_body = self.helper.request_body_from_params(self.params)
except KubernetesException as exc:
self.fail_json(msg="Failed to create object: {0}".format(exc.message))
if not self.check_mode:
try:
k8s_obj = self.helper.create_object(namespace, body=request_body)
except KubernetesException as exc:
self.fail_json(msg="Failed to create object: {0}".format(exc.message),
error=exc.value.get('status'))
return k8s_obj
def _read(self, name, namespace):
k8s_obj = None
try:
k8s_obj = self.helper.get_object(name, namespace)
except KubernetesException as exc:
self.fail_json(msg='Failed to retrieve requested object',
error=exc.value.get('status'))
return k8s_obj
def load_resource_definition(self, src):
""" Load the requested src path """
result = None
path = os.path.normpath(src)
if not os.path.exists(path):
self.fail_json(msg="Error accessing {0}. Does the file exist?".format(path))
try:
result = yaml.safe_load(open(path, 'r'))
except (IOError, yaml.YAMLError) as exc:
self.fail_json(msg="Error loading resource_definition: {0}".format(exc))
return result
def resource_to_parameters(self, resource):
""" Converts a resource definition to module parameters """
parameters = {}
for key, value in iteritems(resource):
if key in ('apiVersion', 'kind', 'status'):
continue
elif key == 'metadata' and isinstance(value, dict):
for meta_key, meta_value in iteritems(value):
if meta_key in ('name', 'namespace', 'labels', 'annotations'):
parameters[meta_key] = meta_value
elif key in self.helper.argspec and value is not None:
parameters[key] = value
elif isinstance(value, dict):
self._add_parameter(value, [key], parameters)
return parameters
def _add_parameter(self, request, path, parameters):
for key, value in iteritems(request):
if path:
param_name = '_'.join(path + [self._to_snake(key)])
else:
param_name = self.helper.attribute_to_snake(key)
if param_name in self.helper.argspec and value is not None:
parameters[param_name] = value
elif isinstance(value, dict):
continue_path = copy.copy(path) if path else []
continue_path.append(self.helper.attribute_to_snake(key))
self._add_parameter(value, continue_path, parameters)
else:
self.fail_json(
msg=("Error parsing resource definition. Encountered {0}, which does not map to a parameter "
"expected by the OpenShift Python module.".format(param_name))
)
@staticmethod
def _to_snake(name):
"""
Convert a string from camel to snake
:param name: string to convert
:return: string
"""
if not name:
return name
def replace(m):
m = m.group(0)
return m[0] + '_' + m[1:]
p = r'[a-z][A-Z]|' \
r'[A-Z]{2}[a-z]'
result = re.sub(p, replace, name)
return result.lower()
class OpenShiftAnsibleModuleHelper(AnsibleMixin, OpenShiftObjectHelper):
pass
class OpenShiftAnsibleModule(KubernetesAnsibleModule):
def __init__(self):
super(OpenShiftAnsibleModule, self).__init__()
@property
def _argspec(self):
args = super(OpenShiftAnsibleModule, self)._argspec
args.update(copy.deepcopy(OPENSHIFT_ARG_SPEC))
return args
def _get_helper(self, api_version, kind):
try:
helper = OpenShiftAnsibleModuleHelper(api_version=api_version, kind=kind, debug=False)
helper.get_model(api_version, kind)
return helper
except KubernetesException as exc:
self.exit_json(msg="Error initializing module helper {}".format(exc.message))
def _create(self, namespace):
if self.kind.lower() == 'project':
return self._create_project()
return super(OpenShiftAnsibleModule, self)._create(namespace)
def _create_project(self):
new_obj = None
k8s_obj = None
try:
new_obj = self.helper.object_from_params(self.params)
except KubernetesException as exc:
self.fail_json(msg="Failed to create object: {}".format(exc.message))
try:
k8s_obj = self.helper.create_project(metadata=new_obj.metadata,
display_name=self.params.get('display_name'),
description=self.params.get('description'))
except KubernetesException as exc:
self.fail_json(msg='Failed to retrieve requested object',
error=exc.value.get('status'))
return k8s_obj

View file

@ -1,5 +1,5 @@
#
# Copyright 2017 Red Hat | Ansible
# Copyright 2018 Red Hat | Ansible
#
# This file is part of Ansible
#
@ -16,34 +16,20 @@
# 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
import os
import re
import copy
import base64
import copy
from ansible.module_utils.six import iteritems, string_types
from keyword import kwlist
from ansible.module_utils.six import iteritems
from ansible.module_utils.basic import AnsibleModule
try:
from openshift.helper import PRIMITIVES
from openshift.helper.kubernetes import KubernetesObjectHelper
from openshift.helper.exceptions import KubernetesException
HAS_K8S_MODULE_HELPER = True
except ImportError as exc:
class KubernetesObjectHelper(object):
pass
HAS_K8S_MODULE_HELPER = False
try:
import yaml
HAS_YAML = True
except ImportError:
HAS_YAML = False
# TODO Remove string_utils dependency
try:
import string_utils
HAS_STRING_UTILS = True
@ -55,7 +41,7 @@ ARG_ATTRIBUTES_BLACKLIST = ('property_path',)
PYTHON_KEYWORD_MAPPING = dict(zip(['_{0}'.format(item) for item in kwlist], kwlist))
PYTHON_KEYWORD_MAPPING.update(dict([reversed(item) for item in iteritems(PYTHON_KEYWORD_MAPPING)]))
ARG_SPEC = {
COMMON_ARG_SPEC = {
'state': {
'default': 'present',
'choices': ['present', 'absent'],
@ -74,12 +60,13 @@ ARG_SPEC = {
'kind': {},
'name': {},
'namespace': {},
'description': {},
'display_name': {},
'api_version': {
'default': 'v1',
'aliases': ['api', 'version'],
},
}
AUTH_ARG_SPEC = {
'kubeconfig': {
'type': 'path',
},
@ -106,6 +93,11 @@ ARG_SPEC = {
},
}
OPENSHIFT_ARG_SPEC = {
'description': {},
'display_name': {},
}
class AnsibleMixin(object):
_argspec_cache = None
@ -118,8 +110,9 @@ class AnsibleMixin(object):
"""
if self._argspec_cache:
return self._argspec_cache
argument_spec = copy.deepcopy(ARG_SPEC)
argument_spec = copy.deepcopy(COMMON_ARG_SPEC)
argument_spec.update(copy.deepcopy(AUTH_ARG_SPEC))
argument_spec.update(copy.deepcopy(OPENSHIFT_ARG_SPEC))
argument_spec.update(self.__transform_properties(self.properties))
self._argspec_cache = argument_spec
return self._argspec_cache
@ -638,275 +631,3 @@ class AnsibleMixin(object):
if len(choices) > 0:
args[arg_prefix + prop]['choices'] = choices
return args
class KubernetesAnsibleModuleHelper(AnsibleMixin, KubernetesObjectHelper):
pass
class KubernetesAnsibleModule(AnsibleModule):
def __init__(self):
if not HAS_K8S_MODULE_HELPER:
raise Exception(
"This module requires the OpenShift Python client. Try `pip install openshift`"
)
if not HAS_YAML:
raise Exception(
"This module requires PyYAML. Try `pip install PyYAML`"
)
if not HAS_STRING_UTILS:
raise Exception(
"This module requires Python string utils. Try `pip install python-string-utils`"
)
mutually_exclusive = [
('resource_definition', 'src'),
]
AnsibleModule.__init__(self,
argument_spec=self._argspec,
supports_check_mode=True,
mutually_exclusive=mutually_exclusive)
self.kind = self.params.pop('kind')
self.api_version = self.params.pop('api_version')
self.resource_definition = self.params.pop('resource_definition')
self.src = self.params.pop('src')
if self.src:
self.resource_definition = self.load_resource_definition(self.src)
if self.resource_definition:
self.api_version = self.resource_definition.get('apiVersion')
self.kind = self.resource_definition.get('kind')
self.api_version = self.api_version.lower()
self.kind = self._to_snake(self.kind)
if not self.api_version:
self.fail_json(
msg=("Error: no api_version specified. Use the api_version parameter, or provide it as part of a ",
"resource_definition.")
)
if not self.kind:
self.fail_json(
msg="Error: no kind specified. Use the kind parameter, or provide it as part of a resource_definition"
)
self.helper = self._get_helper(self.api_version, self.kind)
@property
def _argspec(self):
argspec = copy.deepcopy(ARG_SPEC)
argspec.pop('display_name')
argspec.pop('description')
return argspec
def _get_helper(self, api_version, kind):
try:
helper = KubernetesAnsibleModuleHelper(api_version=api_version, kind=kind, debug=False)
helper.get_model(api_version, kind)
return helper
except KubernetesException as exc:
self.fail_json(msg="Error initializing module helper {0}".format(exc.message))
def execute_module(self):
if self.resource_definition:
resource_params = self.resource_to_parameters(self.resource_definition)
self.params.update(resource_params)
self._authenticate()
state = self.params.pop('state', None)
force = self.params.pop('force', False)
name = self.params.get('name')
namespace = self.params.get('namespace')
existing = None
self._remove_aliases()
return_attributes = dict(changed=False, result=dict())
if self._diff:
return_attributes['request'] = self.helper.request_body_from_params(self.params)
if self.helper.base_model_name_snake.endswith('list'):
k8s_obj = self._read(name, namespace)
return_attributes['result'] = k8s_obj.to_dict()
self.exit_json(**return_attributes)
try:
existing = self.helper.get_object(name, namespace)
except KubernetesException as exc:
self.fail_json(msg='Failed to retrieve requested object: {0}'.format(exc.message),
error=exc.value.get('status'))
if state == 'absent':
if not existing:
# The object already does not exist
self.exit_json(**return_attributes)
else:
# Delete the object
if not self.check_mode:
try:
self.helper.delete_object(name, namespace)
except KubernetesException as exc:
self.fail_json(msg="Failed to delete object: {0}".format(exc.message),
error=exc.value.get('status'))
return_attributes['changed'] = True
self.exit_json(**return_attributes)
else:
if not existing:
k8s_obj = self._create(namespace)
return_attributes['result'] = k8s_obj.to_dict()
return_attributes['changed'] = True
self.exit_json(**return_attributes)
if existing and force:
k8s_obj = None
request_body = self.helper.request_body_from_params(self.params)
if not self.check_mode:
try:
k8s_obj = self.helper.replace_object(name, namespace, body=request_body)
except KubernetesException as exc:
self.fail_json(msg="Failed to replace object: {0}".format(exc.message),
error=exc.value.get('status'))
return_attributes['result'] = k8s_obj.to_dict()
return_attributes['changed'] = True
self.exit_json(**return_attributes)
# Check if existing object should be patched
k8s_obj = copy.deepcopy(existing)
try:
self.helper.object_from_params(self.params, obj=k8s_obj)
except KubernetesException as exc:
self.fail_json(msg="Failed to patch object: {0}".format(exc.message))
match, diff = self.helper.objects_match(existing, k8s_obj)
if match:
return_attributes['result'] = existing.to_dict()
self.exit_json(**return_attributes)
elif self._diff:
return_attributes['differences'] = diff
# Differences exist between the existing obj and requested params
if not self.check_mode:
try:
k8s_obj = self.helper.patch_object(name, namespace, k8s_obj)
except KubernetesException as exc:
self.fail_json(msg="Failed to patch object: {0}".format(exc.message))
return_attributes['result'] = k8s_obj.to_dict()
return_attributes['changed'] = True
self.exit_json(**return_attributes)
def _authenticate(self):
try:
auth_options = {}
auth_args = ('host', 'api_key', 'kubeconfig', 'context', 'username', 'password',
'cert_file', 'key_file', 'ssl_ca_cert', 'verify_ssl')
for key, value in iteritems(self.params):
if key in auth_args and value is not None:
auth_options[key] = value
self.helper.set_client_config(**auth_options)
except KubernetesException as e:
self.fail_json(msg='Error loading config', error=str(e))
def _remove_aliases(self):
"""
The helper doesn't know what to do with aliased keys
"""
for k, v in iteritems(self._argspec):
if 'aliases' in v:
for alias in v['aliases']:
if alias in self.params:
self.params.pop(alias)
def _create(self, namespace):
request_body = None
k8s_obj = None
try:
request_body = self.helper.request_body_from_params(self.params)
except KubernetesException as exc:
self.fail_json(msg="Failed to create object: {0}".format(exc.message))
if not self.check_mode:
try:
k8s_obj = self.helper.create_object(namespace, body=request_body)
except KubernetesException as exc:
self.fail_json(msg="Failed to create object: {0}".format(exc.message),
error=exc.value.get('status'))
return k8s_obj
def _read(self, name, namespace):
k8s_obj = None
try:
k8s_obj = self.helper.get_object(name, namespace)
except KubernetesException as exc:
self.fail_json(msg='Failed to retrieve requested object',
error=exc.value.get('status'))
return k8s_obj
def load_resource_definition(self, src):
""" Load the requested src path """
result = None
path = os.path.normpath(src)
if not os.path.exists(path):
self.fail_json(msg="Error accessing {0}. Does the file exist?".format(path))
try:
result = yaml.safe_load(open(path, 'r'))
except (IOError, yaml.YAMLError) as exc:
self.fail_json(msg="Error loading resource_definition: {0}".format(exc))
return result
def resource_to_parameters(self, resource):
""" Converts a resource definition to module parameters """
parameters = {}
for key, value in iteritems(resource):
if key in ('apiVersion', 'kind', 'status'):
continue
elif key == 'metadata' and isinstance(value, dict):
for meta_key, meta_value in iteritems(value):
if meta_key in ('name', 'namespace', 'labels', 'annotations'):
parameters[meta_key] = meta_value
elif key in self.helper.argspec and value is not None:
parameters[key] = value
elif isinstance(value, dict):
self._add_parameter(value, [key], parameters)
return parameters
def _add_parameter(self, request, path, parameters):
for key, value in iteritems(request):
if path:
param_name = '_'.join(path + [self._to_snake(key)])
else:
param_name = self.helper.attribute_to_snake(key)
if param_name in self.helper.argspec and value is not None:
parameters[param_name] = value
elif isinstance(value, dict):
continue_path = copy.copy(path) if path else []
continue_path.append(self.helper.attribute_to_snake(key))
self._add_parameter(value, continue_path, parameters)
else:
self.fail_json(
msg=("Error parsing resource definition. Encountered {0}, which does not map to a parameter "
"expected by the OpenShift Python module.".format(param_name))
)
@staticmethod
def _to_snake(name):
"""
Convert a string from camel to snake
:param name: string to convert
:return: string
"""
if not name:
return name
def replace(m):
m = m.group(0)
return m[0] + '_' + m[1:]
p = r'[a-z][A-Z]|' \
r'[A-Z]{2}[a-z]'
result = re.sub(p, replace, name)
return result.lower()

View file

@ -1,78 +0,0 @@
#
# Copyright 2018 Red Hat | Ansible
#
# 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/>.
import copy
from ansible.module_utils.k8s_common import KubernetesAnsibleModule, AnsibleMixin, ARG_SPEC
try:
from openshift.helper.openshift import OpenShiftObjectHelper
from openshift.helper.exceptions import KubernetesException
HAS_OPENSHIFT_HELPER = True
except ImportError as exc:
class OpenShiftObjectHelper(object):
pass
HAS_OPENSHIFT_HELPER = False
class OpenShiftAnsibleModuleHelper(AnsibleMixin, OpenShiftObjectHelper):
pass
class OpenShiftAnsibleModule(KubernetesAnsibleModule):
def __init__(self):
if not HAS_OPENSHIFT_HELPER:
raise Exception(
"This module requires the OpenShift Python client. Try `pip install openshift`"
)
super(OpenShiftAnsibleModule, self).__init__()
@property
def _argspec(self):
return copy.deepcopy(ARG_SPEC)
def _get_helper(self, api_version, kind):
try:
helper = OpenShiftAnsibleModuleHelper(api_version=api_version, kind=kind, debug=False)
helper.get_model(api_version, kind)
return helper
except KubernetesException as exc:
self.exit_json(msg="Error initializing module helper {}".format(exc.message))
def _create(self, namespace):
if self.kind.lower() == 'project':
return self._create_project()
return super(OpenShiftAnsibleModule, self)._create(namespace)
def _create_project(self):
new_obj = None
k8s_obj = None
try:
new_obj = self.helper.object_from_params(self.params)
except KubernetesException as exc:
self.fail_json(msg="Failed to create object: {}".format(exc.message))
try:
k8s_obj = self.helper.create_project(metadata=new_obj.metadata,
display_name=self.params.get('display_name'),
description=self.params.get('description'))
except KubernetesException as exc:
self.fail_json(msg='Failed to retrieve requested object',
error=exc.value.get('status'))
return k8s_obj

View file

@ -136,7 +136,7 @@ diff:
type: list
'''
from ansible.module_utils.k8s_common import KubernetesAnsibleModule
from ansible.module_utils.k8s.common import KubernetesAnsibleModule
def main():

View file

@ -176,7 +176,7 @@ diff:
type: list
'''
from ansible.module_utils.openshift_common import OpenShiftAnsibleModule
from ansible.module_utils.k8s.common import OpenShiftAnsibleModule
def main():