mirror of
https://github.com/ansible-collections/kubernetes.core.git
synced 2026-05-06 13:02:37 +00:00
Enable black formatting test (#259)
Enable black formatting test SUMMARY Signed-off-by: Abhijeet Kasurde akasurde@redhat.com ISSUE TYPE Bugfix Pull Request COMPONENT NAME plugins/action/k8s_info.py plugins/connection/kubectl.py plugins/doc_fragments/helm_common_options.py plugins/doc_fragments/k8s_auth_options.py plugins/doc_fragments/k8s_delete_options.py plugins/doc_fragments/k8s_name_options.py plugins/doc_fragments/k8s_resource_options.py plugins/doc_fragments/k8s_scale_options.py plugins/doc_fragments/k8s_state_options.py plugins/doc_fragments/k8s_wait_options.py plugins/filter/k8s.py plugins/inventory/k8s.py plugins/lookup/k8s.py plugins/lookup/kustomize.py plugins/module_utils/ansiblemodule.py plugins/module_utils/apply.py plugins/module_utils/args_common.py plugins/module_utils/client/discovery.py plugins/module_utils/client/resource.py plugins/module_utils/common.py plugins/module_utils/exceptions.py plugins/module_utils/hashes.py plugins/module_utils/helm.py plugins/module_utils/k8sdynamicclient.py plugins/module_utils/selector.py plugins/modules/helm.py plugins/modules/helm_info.py plugins/modules/helm_plugin.py plugins/modules/helm_plugin_info.py plugins/modules/helm_repository.py plugins/modules/helm_template.py plugins/modules/k8s.py plugins/modules/k8s_cluster_info.py plugins/modules/k8s_cp.py plugins/modules/k8s_drain.py plugins/modules/k8s_exec.py plugins/modules/k8s_info.py plugins/modules/k8s_json_patch.py plugins/modules/k8s_log.py plugins/modules/k8s_rollback.py plugins/modules/k8s_scale.py plugins/modules/k8s_service.py tests/integration/targets/kubernetes/library/test_tempfile.py tests/unit/module_utils/test_apply.py tests/unit/module_utils/test_common.py tests/unit/module_utils/test_discoverer.py tests/unit/module_utils/test_hashes.py tests/unit/module_utils/test_marshal.py tests/unit/module_utils/test_selector.py tox.ini Reviewed-by: None <None> Reviewed-by: Mike Graves <mgraves@redhat.com> Reviewed-by: None <None>
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
from __future__ import absolute_import, division, print_function
|
||||
|
||||
__metaclass__ = type
|
||||
|
||||
@@ -17,6 +17,7 @@ if enable_turbo_mode:
|
||||
from ansible_collections.cloud.common.plugins.module_utils.turbo.module import (
|
||||
AnsibleTurboModule as AnsibleModule,
|
||||
) # noqa: F401
|
||||
|
||||
AnsibleModule.collection_name = "kubernetes.core"
|
||||
except ImportError:
|
||||
from ansible.module_utils.basic import AnsibleModule # noqa: F401
|
||||
|
||||
@@ -14,13 +14,16 @@
|
||||
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
|
||||
__metaclass__ = type
|
||||
|
||||
from collections import OrderedDict
|
||||
import json
|
||||
|
||||
from ansible.module_utils.common.dict_transformations import dict_merge
|
||||
from ansible_collections.kubernetes.core.plugins.module_utils.exceptions import ApplyException
|
||||
from ansible_collections.kubernetes.core.plugins.module_utils.exceptions import (
|
||||
ApplyException,
|
||||
)
|
||||
|
||||
try:
|
||||
from kubernetes.dynamic.exceptions import NotFoundError
|
||||
@@ -28,50 +31,52 @@ except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
LAST_APPLIED_CONFIG_ANNOTATION = 'kubectl.kubernetes.io/last-applied-configuration'
|
||||
LAST_APPLIED_CONFIG_ANNOTATION = "kubectl.kubernetes.io/last-applied-configuration"
|
||||
|
||||
POD_SPEC_SUFFIXES = {
|
||||
'containers': 'name',
|
||||
'initContainers': 'name',
|
||||
'ephemeralContainers': 'name',
|
||||
'volumes': 'name',
|
||||
'imagePullSecrets': 'name',
|
||||
'containers.volumeMounts': 'mountPath',
|
||||
'containers.volumeDevices': 'devicePath',
|
||||
'containers.env': 'name',
|
||||
'containers.ports': 'containerPort',
|
||||
'initContainers.volumeMounts': 'mountPath',
|
||||
'initContainers.volumeDevices': 'devicePath',
|
||||
'initContainers.env': 'name',
|
||||
'initContainers.ports': 'containerPort',
|
||||
'ephemeralContainers.volumeMounts': 'mountPath',
|
||||
'ephemeralContainers.volumeDevices': 'devicePath',
|
||||
'ephemeralContainers.env': 'name',
|
||||
'ephemeralContainers.ports': 'containerPort',
|
||||
"containers": "name",
|
||||
"initContainers": "name",
|
||||
"ephemeralContainers": "name",
|
||||
"volumes": "name",
|
||||
"imagePullSecrets": "name",
|
||||
"containers.volumeMounts": "mountPath",
|
||||
"containers.volumeDevices": "devicePath",
|
||||
"containers.env": "name",
|
||||
"containers.ports": "containerPort",
|
||||
"initContainers.volumeMounts": "mountPath",
|
||||
"initContainers.volumeDevices": "devicePath",
|
||||
"initContainers.env": "name",
|
||||
"initContainers.ports": "containerPort",
|
||||
"ephemeralContainers.volumeMounts": "mountPath",
|
||||
"ephemeralContainers.volumeDevices": "devicePath",
|
||||
"ephemeralContainers.env": "name",
|
||||
"ephemeralContainers.ports": "containerPort",
|
||||
}
|
||||
|
||||
POD_SPEC_PREFIXES = [
|
||||
'Pod.spec',
|
||||
'Deployment.spec.template.spec',
|
||||
'DaemonSet.spec.template.spec',
|
||||
'StatefulSet.spec.template.spec',
|
||||
'Job.spec.template.spec',
|
||||
'Cronjob.spec.jobTemplate.spec.template.spec',
|
||||
"Pod.spec",
|
||||
"Deployment.spec.template.spec",
|
||||
"DaemonSet.spec.template.spec",
|
||||
"StatefulSet.spec.template.spec",
|
||||
"Job.spec.template.spec",
|
||||
"Cronjob.spec.jobTemplate.spec.template.spec",
|
||||
]
|
||||
|
||||
# patch merge keys taken from generated.proto files under
|
||||
# staging/src/k8s.io/api in kubernetes/kubernetes
|
||||
STRATEGIC_MERGE_PATCH_KEYS = {
|
||||
'Service.spec.ports': 'port',
|
||||
'ServiceAccount.secrets': 'name',
|
||||
'ValidatingWebhookConfiguration.webhooks': 'name',
|
||||
'MutatingWebhookConfiguration.webhooks': 'name',
|
||||
"Service.spec.ports": "port",
|
||||
"ServiceAccount.secrets": "name",
|
||||
"ValidatingWebhookConfiguration.webhooks": "name",
|
||||
"MutatingWebhookConfiguration.webhooks": "name",
|
||||
}
|
||||
|
||||
STRATEGIC_MERGE_PATCH_KEYS.update(
|
||||
{"%s.%s" % (prefix, key): value
|
||||
for prefix in POD_SPEC_PREFIXES
|
||||
for key, value in POD_SPEC_SUFFIXES.items()}
|
||||
{
|
||||
"%s.%s" % (prefix, key): value
|
||||
for prefix in POD_SPEC_PREFIXES
|
||||
for key, value in POD_SPEC_SUFFIXES.items()
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -79,21 +84,28 @@ def annotate(desired):
|
||||
return dict(
|
||||
metadata=dict(
|
||||
annotations={
|
||||
LAST_APPLIED_CONFIG_ANNOTATION: json.dumps(desired, separators=(',', ':'), indent=None, sort_keys=True)
|
||||
LAST_APPLIED_CONFIG_ANNOTATION: json.dumps(
|
||||
desired, separators=(",", ":"), indent=None, sort_keys=True
|
||||
)
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def apply_patch(actual, desired):
|
||||
last_applied = actual['metadata'].get('annotations', {}).get(LAST_APPLIED_CONFIG_ANNOTATION)
|
||||
last_applied = (
|
||||
actual["metadata"].get("annotations", {}).get(LAST_APPLIED_CONFIG_ANNOTATION)
|
||||
)
|
||||
|
||||
if last_applied:
|
||||
# ensure that last_applied doesn't come back as a dict of unicode key/value pairs
|
||||
# json.loads can be used if we stop supporting python 2
|
||||
last_applied = json.loads(last_applied)
|
||||
patch = merge(dict_merge(last_applied, annotate(last_applied)),
|
||||
dict_merge(desired, annotate(desired)), actual)
|
||||
patch = merge(
|
||||
dict_merge(last_applied, annotate(last_applied)),
|
||||
dict_merge(desired, annotate(desired)),
|
||||
actual,
|
||||
)
|
||||
if patch:
|
||||
return actual, patch
|
||||
else:
|
||||
@@ -104,7 +116,10 @@ def apply_patch(actual, desired):
|
||||
|
||||
def apply_object(resource, definition):
|
||||
try:
|
||||
actual = resource.get(name=definition['metadata']['name'], namespace=definition['metadata'].get('namespace'))
|
||||
actual = resource.get(
|
||||
name=definition["metadata"]["name"],
|
||||
namespace=definition["metadata"].get("namespace"),
|
||||
)
|
||||
except NotFoundError:
|
||||
return None, dict_merge(definition, annotate(definition))
|
||||
return apply_patch(actual.to_dict(), definition)
|
||||
@@ -113,14 +128,21 @@ def apply_object(resource, definition):
|
||||
def k8s_apply(resource, definition, **kwargs):
|
||||
existing, desired = apply_object(resource, definition)
|
||||
if not existing:
|
||||
return resource.create(body=desired, namespace=definition['metadata'].get('namespace'), **kwargs)
|
||||
return resource.create(
|
||||
body=desired, namespace=definition["metadata"].get("namespace"), **kwargs
|
||||
)
|
||||
if existing == desired:
|
||||
return resource.get(name=definition['metadata']['name'], namespace=definition['metadata'].get('namespace'))
|
||||
return resource.patch(body=desired,
|
||||
name=definition['metadata']['name'],
|
||||
namespace=definition['metadata'].get('namespace'),
|
||||
content_type='application/merge-patch+json',
|
||||
**kwargs)
|
||||
return resource.get(
|
||||
name=definition["metadata"]["name"],
|
||||
namespace=definition["metadata"].get("namespace"),
|
||||
)
|
||||
return resource.patch(
|
||||
body=desired,
|
||||
name=definition["metadata"]["name"],
|
||||
namespace=definition["metadata"].get("namespace"),
|
||||
content_type="application/merge-patch+json",
|
||||
**kwargs
|
||||
)
|
||||
|
||||
|
||||
# The patch is the difference from actual to desired without deletions, plus deletions
|
||||
@@ -129,7 +151,7 @@ def k8s_apply(resource, definition, **kwargs):
|
||||
# deletions, and then apply delta to deletions as a patch, which should be strictly additive.
|
||||
def merge(last_applied, desired, actual, position=None):
|
||||
deletions = get_deletions(last_applied, desired)
|
||||
delta = get_delta(last_applied, actual, desired, position or desired['kind'])
|
||||
delta = get_delta(last_applied, actual, desired, position or desired["kind"])
|
||||
return dict_merge(deletions, delta)
|
||||
|
||||
|
||||
@@ -139,7 +161,9 @@ def list_to_dict(lst, key, position):
|
||||
try:
|
||||
result[item[key]] = item
|
||||
except KeyError:
|
||||
raise ApplyException("Expected key '%s' not found in position %s" % (key, position))
|
||||
raise ApplyException(
|
||||
"Expected key '%s' not found in position %s" % (key, position)
|
||||
)
|
||||
return result
|
||||
|
||||
|
||||
@@ -158,7 +182,12 @@ def list_merge(last_applied, actual, desired, position):
|
||||
if key not in actual_dict or key not in last_applied_dict:
|
||||
result.append(desired_dict[key])
|
||||
else:
|
||||
patch = merge(last_applied_dict[key], desired_dict[key], actual_dict[key], position)
|
||||
patch = merge(
|
||||
last_applied_dict[key],
|
||||
desired_dict[key],
|
||||
actual_dict[key],
|
||||
position,
|
||||
)
|
||||
result.append(dict_merge(actual_dict[key], patch))
|
||||
for key in actual_dict:
|
||||
if key not in desired_dict and key not in last_applied_dict:
|
||||
@@ -198,11 +227,11 @@ def recursive_list_diff(list1, list2, position=None):
|
||||
|
||||
def recursive_diff(dict1, dict2, position=None):
|
||||
if not position:
|
||||
if 'kind' in dict1 and dict1.get('kind') == dict2.get('kind'):
|
||||
position = dict1['kind']
|
||||
if "kind" in dict1 and dict1.get("kind") == dict2.get("kind"):
|
||||
position = dict1["kind"]
|
||||
left = dict((k, v) for (k, v) in dict1.items() if k not in dict2)
|
||||
right = dict((k, v) for (k, v) in dict2.items() if k not in dict1)
|
||||
for k in (set(dict1.keys()) & set(dict2.keys())):
|
||||
for k in set(dict1.keys()) & set(dict2.keys()):
|
||||
if position:
|
||||
this_position = "%s.%s" % (position, k)
|
||||
if isinstance(dict1[k], dict) and isinstance(dict2[k], dict):
|
||||
@@ -247,11 +276,15 @@ def get_delta(last_applied, actual, desired, position=None):
|
||||
if actual_value is None:
|
||||
patch[k] = desired_value
|
||||
elif isinstance(desired_value, dict):
|
||||
p = get_delta(last_applied.get(k, {}), actual_value, desired_value, this_position)
|
||||
p = get_delta(
|
||||
last_applied.get(k, {}), actual_value, desired_value, this_position
|
||||
)
|
||||
if p:
|
||||
patch[k] = p
|
||||
elif isinstance(desired_value, list):
|
||||
p = list_merge(last_applied.get(k, []), actual_value, desired_value, this_position)
|
||||
p = list_merge(
|
||||
last_applied.get(k, []), actual_value, desired_value, this_position
|
||||
)
|
||||
if p:
|
||||
patch[k] = [item for item in p if item is not None]
|
||||
elif actual_value != desired_value:
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
from __future__ import absolute_import, division, print_function
|
||||
|
||||
from ansible.module_utils.six import string_types
|
||||
|
||||
@@ -12,133 +12,83 @@ def list_dict_str(value):
|
||||
|
||||
|
||||
AUTH_PROXY_HEADERS_SPEC = dict(
|
||||
proxy_basic_auth=dict(type='str', no_log=True),
|
||||
basic_auth=dict(type='str', no_log=True),
|
||||
user_agent=dict(type='str')
|
||||
proxy_basic_auth=dict(type="str", no_log=True),
|
||||
basic_auth=dict(type="str", no_log=True),
|
||||
user_agent=dict(type="str"),
|
||||
)
|
||||
|
||||
AUTH_ARG_SPEC = {
|
||||
'kubeconfig': {
|
||||
'type': 'raw',
|
||||
},
|
||||
'context': {},
|
||||
'host': {},
|
||||
'api_key': {
|
||||
'no_log': True,
|
||||
},
|
||||
'username': {},
|
||||
'password': {
|
||||
'no_log': True,
|
||||
},
|
||||
'validate_certs': {
|
||||
'type': 'bool',
|
||||
'aliases': ['verify_ssl'],
|
||||
},
|
||||
'ca_cert': {
|
||||
'type': 'path',
|
||||
'aliases': ['ssl_ca_cert'],
|
||||
},
|
||||
'client_cert': {
|
||||
'type': 'path',
|
||||
'aliases': ['cert_file'],
|
||||
},
|
||||
'client_key': {
|
||||
'type': 'path',
|
||||
'aliases': ['key_file'],
|
||||
},
|
||||
'proxy': {
|
||||
'type': 'str',
|
||||
},
|
||||
'proxy_headers': {
|
||||
'type': 'dict',
|
||||
'options': AUTH_PROXY_HEADERS_SPEC
|
||||
},
|
||||
'persist_config': {
|
||||
'type': 'bool',
|
||||
},
|
||||
"kubeconfig": {"type": "raw"},
|
||||
"context": {},
|
||||
"host": {},
|
||||
"api_key": {"no_log": True},
|
||||
"username": {},
|
||||
"password": {"no_log": True},
|
||||
"validate_certs": {"type": "bool", "aliases": ["verify_ssl"]},
|
||||
"ca_cert": {"type": "path", "aliases": ["ssl_ca_cert"]},
|
||||
"client_cert": {"type": "path", "aliases": ["cert_file"]},
|
||||
"client_key": {"type": "path", "aliases": ["key_file"]},
|
||||
"proxy": {"type": "str"},
|
||||
"proxy_headers": {"type": "dict", "options": AUTH_PROXY_HEADERS_SPEC},
|
||||
"persist_config": {"type": "bool"},
|
||||
}
|
||||
|
||||
WAIT_ARG_SPEC = dict(
|
||||
wait=dict(type='bool', default=False),
|
||||
wait_sleep=dict(type='int', default=5),
|
||||
wait_timeout=dict(type='int', default=120),
|
||||
wait=dict(type="bool", default=False),
|
||||
wait_sleep=dict(type="int", default=5),
|
||||
wait_timeout=dict(type="int", default=120),
|
||||
wait_condition=dict(
|
||||
type='dict',
|
||||
type="dict",
|
||||
default=None,
|
||||
options=dict(
|
||||
type=dict(),
|
||||
status=dict(default=True, choices=[True, False, "Unknown"]),
|
||||
reason=dict()
|
||||
)
|
||||
)
|
||||
reason=dict(),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
# Map kubernetes-client parameters to ansible parameters
|
||||
AUTH_ARG_MAP = {
|
||||
'kubeconfig': 'kubeconfig',
|
||||
'context': 'context',
|
||||
'host': 'host',
|
||||
'api_key': 'api_key',
|
||||
'username': 'username',
|
||||
'password': 'password',
|
||||
'verify_ssl': 'validate_certs',
|
||||
'ssl_ca_cert': 'ca_cert',
|
||||
'cert_file': 'client_cert',
|
||||
'key_file': 'client_key',
|
||||
'proxy': 'proxy',
|
||||
'proxy_headers': 'proxy_headers',
|
||||
'persist_config': 'persist_config',
|
||||
"kubeconfig": "kubeconfig",
|
||||
"context": "context",
|
||||
"host": "host",
|
||||
"api_key": "api_key",
|
||||
"username": "username",
|
||||
"password": "password",
|
||||
"verify_ssl": "validate_certs",
|
||||
"ssl_ca_cert": "ca_cert",
|
||||
"cert_file": "client_cert",
|
||||
"key_file": "client_key",
|
||||
"proxy": "proxy",
|
||||
"proxy_headers": "proxy_headers",
|
||||
"persist_config": "persist_config",
|
||||
}
|
||||
|
||||
NAME_ARG_SPEC = {
|
||||
'kind': {},
|
||||
'name': {},
|
||||
'namespace': {},
|
||||
'api_version': {
|
||||
'default': 'v1',
|
||||
'aliases': ['api', 'version'],
|
||||
},
|
||||
"kind": {},
|
||||
"name": {},
|
||||
"namespace": {},
|
||||
"api_version": {"default": "v1", "aliases": ["api", "version"]},
|
||||
}
|
||||
|
||||
COMMON_ARG_SPEC = {
|
||||
'state': {
|
||||
'default': 'present',
|
||||
'choices': ['present', 'absent'],
|
||||
},
|
||||
'force': {
|
||||
'type': 'bool',
|
||||
'default': False,
|
||||
},
|
||||
"state": {"default": "present", "choices": ["present", "absent"]},
|
||||
"force": {"type": "bool", "default": False},
|
||||
}
|
||||
|
||||
RESOURCE_ARG_SPEC = {
|
||||
'resource_definition': {
|
||||
'type': list_dict_str,
|
||||
'aliases': ['definition', 'inline']
|
||||
},
|
||||
'src': {
|
||||
'type': 'path',
|
||||
},
|
||||
"resource_definition": {"type": list_dict_str, "aliases": ["definition", "inline"]},
|
||||
"src": {"type": "path"},
|
||||
}
|
||||
|
||||
ARG_ATTRIBUTES_BLACKLIST = ('property_path',)
|
||||
ARG_ATTRIBUTES_BLACKLIST = ("property_path",)
|
||||
|
||||
DELETE_OPTS_ARG_SPEC = {
|
||||
'propagationPolicy': {
|
||||
'choices': ['Foreground', 'Background', 'Orphan'],
|
||||
"propagationPolicy": {"choices": ["Foreground", "Background", "Orphan"]},
|
||||
"gracePeriodSeconds": {"type": "int"},
|
||||
"preconditions": {
|
||||
"type": "dict",
|
||||
"options": {"resourceVersion": {"type": "str"}, "uid": {"type": "str"}},
|
||||
},
|
||||
'gracePeriodSeconds': {
|
||||
'type': 'int',
|
||||
},
|
||||
'preconditions': {
|
||||
'type': 'dict',
|
||||
'options': {
|
||||
'resourceVersion': {
|
||||
'type': 'str',
|
||||
},
|
||||
'uid': {
|
||||
'type': 'str',
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,17 +23,26 @@ from functools import partial
|
||||
import kubernetes.dynamic
|
||||
import kubernetes.dynamic.discovery
|
||||
from kubernetes import __version__
|
||||
from kubernetes.dynamic.exceptions import (ResourceNotFoundError, ResourceNotUniqueError,
|
||||
ServiceUnavailableError)
|
||||
from kubernetes.dynamic.exceptions import (
|
||||
ResourceNotFoundError,
|
||||
ResourceNotUniqueError,
|
||||
ServiceUnavailableError,
|
||||
)
|
||||
|
||||
from ansible_collections.kubernetes.core.plugins.module_utils.client.resource import ResourceList
|
||||
from ansible_collections.kubernetes.core.plugins.module_utils.client.resource import (
|
||||
ResourceList,
|
||||
)
|
||||
|
||||
|
||||
class Discoverer(kubernetes.dynamic.discovery.Discoverer):
|
||||
def __init__(self, client, cache_file):
|
||||
self.client = client
|
||||
default_cache_file_name = 'k8srcp-{0}.json'.format(hashlib.sha256(self.__get_default_cache_id()).hexdigest())
|
||||
self.__cache_file = cache_file or os.path.join(tempfile.gettempdir(), default_cache_file_name)
|
||||
default_cache_file_name = "k8srcp-{0}.json".format(
|
||||
hashlib.sha256(self.__get_default_cache_id()).hexdigest()
|
||||
)
|
||||
self.__cache_file = cache_file or os.path.join(
|
||||
tempfile.gettempdir(), default_cache_file_name
|
||||
)
|
||||
self.__init_cache()
|
||||
|
||||
def __get_default_cache_id(self):
|
||||
@@ -42,21 +51,21 @@ class Discoverer(kubernetes.dynamic.discovery.Discoverer):
|
||||
cache_id = "{0}-{1}".format(self.client.configuration.host, user)
|
||||
else:
|
||||
cache_id = self.client.configuration.host
|
||||
return cache_id.encode('utf-8')
|
||||
return cache_id.encode("utf-8")
|
||||
|
||||
def __get_user(self):
|
||||
# This is intended to provide a portable method for getting a username.
|
||||
# It could, and maybe should, be replaced by getpass.getuser() but, due
|
||||
# to a lack of portability testing the original code is being left in
|
||||
# place.
|
||||
if hasattr(os, 'getlogin'):
|
||||
if hasattr(os, "getlogin"):
|
||||
try:
|
||||
user = os.getlogin()
|
||||
if user:
|
||||
return str(user)
|
||||
except OSError:
|
||||
pass
|
||||
if hasattr(os, 'getuid'):
|
||||
if hasattr(os, "getuid"):
|
||||
try:
|
||||
user = os.getuid()
|
||||
if user:
|
||||
@@ -70,13 +79,13 @@ class Discoverer(kubernetes.dynamic.discovery.Discoverer):
|
||||
|
||||
def __init_cache(self, refresh=False):
|
||||
if refresh or not os.path.exists(self.__cache_file):
|
||||
self._cache = {'library_version': __version__}
|
||||
self._cache = {"library_version": __version__}
|
||||
refresh = True
|
||||
else:
|
||||
try:
|
||||
with open(self.__cache_file, 'r') as f:
|
||||
with open(self.__cache_file, "r") as f:
|
||||
self._cache = json.load(f, cls=partial(CacheDecoder, self.client))
|
||||
if self._cache.get('library_version') != __version__:
|
||||
if self._cache.get("library_version") != __version__:
|
||||
# Version mismatch, need to refresh cache
|
||||
self.invalidate_cache()
|
||||
except Exception:
|
||||
@@ -92,21 +101,25 @@ class Discoverer(kubernetes.dynamic.discovery.Discoverer):
|
||||
resources = defaultdict(list)
|
||||
subresources = defaultdict(dict)
|
||||
|
||||
path = '/'.join(filter(None, [prefix, group, version]))
|
||||
path = "/".join(filter(None, [prefix, group, version]))
|
||||
try:
|
||||
resources_response = self.client.request('GET', path).resources or []
|
||||
resources_response = self.client.request("GET", path).resources or []
|
||||
except ServiceUnavailableError:
|
||||
resources_response = []
|
||||
|
||||
resources_raw = list(filter(lambda resource: '/' not in resource['name'], resources_response))
|
||||
subresources_raw = list(filter(lambda resource: '/' in resource['name'], resources_response))
|
||||
resources_raw = list(
|
||||
filter(lambda resource: "/" not in resource["name"], resources_response)
|
||||
)
|
||||
subresources_raw = list(
|
||||
filter(lambda resource: "/" in resource["name"], resources_response)
|
||||
)
|
||||
for subresource in subresources_raw:
|
||||
resource, name = subresource['name'].split('/')
|
||||
resource, name = subresource["name"].split("/")
|
||||
subresources[resource][name] = subresource
|
||||
|
||||
for resource in resources_raw:
|
||||
# Prevent duplicate keys
|
||||
for key in ('prefix', 'group', 'api_version', 'client', 'preferred'):
|
||||
for key in ("prefix", "group", "api_version", "client", "preferred"):
|
||||
resource.pop(key, None)
|
||||
|
||||
resourceobj = kubernetes.dynamic.Resource(
|
||||
@@ -115,19 +128,25 @@ class Discoverer(kubernetes.dynamic.discovery.Discoverer):
|
||||
api_version=version,
|
||||
client=self.client,
|
||||
preferred=preferred,
|
||||
subresources=subresources.get(resource['name']),
|
||||
subresources=subresources.get(resource["name"]),
|
||||
**resource
|
||||
)
|
||||
resources[resource['kind']].append(resourceobj)
|
||||
resources[resource["kind"]].append(resourceobj)
|
||||
|
||||
resource_lookup = {
|
||||
'prefix': prefix,
|
||||
'group': group,
|
||||
'api_version': version,
|
||||
'kind': resourceobj.kind,
|
||||
'name': resourceobj.name
|
||||
"prefix": prefix,
|
||||
"group": group,
|
||||
"api_version": version,
|
||||
"kind": resourceobj.kind,
|
||||
"name": resourceobj.name,
|
||||
}
|
||||
resource_list = ResourceList(self.client, group=group, api_version=version, base_kind=resource['kind'], base_resource_lookup=resource_lookup)
|
||||
resource_list = ResourceList(
|
||||
self.client,
|
||||
group=group,
|
||||
api_version=version,
|
||||
base_kind=resource["kind"],
|
||||
base_resource_lookup=resource_lookup,
|
||||
)
|
||||
resources[resource_list.kind].append(resource_list)
|
||||
return resources
|
||||
|
||||
@@ -139,23 +158,32 @@ class Discoverer(kubernetes.dynamic.discovery.Discoverer):
|
||||
"""
|
||||
results = self.search(**kwargs)
|
||||
# If there are multiple matches, prefer exact matches on api_version
|
||||
if len(results) > 1 and kwargs.get('api_version'):
|
||||
if len(results) > 1 and kwargs.get("api_version"):
|
||||
results = [
|
||||
result for result in results if result.group_version == kwargs['api_version']
|
||||
result
|
||||
for result in results
|
||||
if result.group_version == kwargs["api_version"]
|
||||
]
|
||||
# If there are multiple matches, prefer non-List kinds
|
||||
if len(results) > 1 and not all(isinstance(x, ResourceList) for x in results):
|
||||
results = [result for result in results if not isinstance(result, ResourceList)]
|
||||
results = [
|
||||
result for result in results if not isinstance(result, ResourceList)
|
||||
]
|
||||
# if multiple resources are found that share a GVK, prefer the one with the most supported verbs
|
||||
if len(results) > 1 and len(set((x.group_version, x.kind) for x in results)) == 1:
|
||||
if (
|
||||
len(results) > 1
|
||||
and len(set((x.group_version, x.kind) for x in results)) == 1
|
||||
):
|
||||
if len(set(len(x.verbs) for x in results)) != 1:
|
||||
results = [max(results, key=lambda x: len(x.verbs))]
|
||||
if len(results) == 1:
|
||||
return results[0]
|
||||
elif not results:
|
||||
raise ResourceNotFoundError('No matches found for {0}'.format(kwargs))
|
||||
raise ResourceNotFoundError("No matches found for {0}".format(kwargs))
|
||||
else:
|
||||
raise ResourceNotUniqueError('Multiple matches found for {0}: {1}'.format(kwargs, results))
|
||||
raise ResourceNotUniqueError(
|
||||
"Multiple matches found for {0}: {1}".format(kwargs, results)
|
||||
)
|
||||
|
||||
|
||||
class LazyDiscoverer(Discoverer, kubernetes.dynamic.LazyDiscoverer):
|
||||
@@ -174,13 +202,15 @@ class CacheDecoder(json.JSONDecoder):
|
||||
json.JSONDecoder.__init__(self, object_hook=self.object_hook, *args, **kwargs)
|
||||
|
||||
def object_hook(self, obj):
|
||||
if '_type' not in obj:
|
||||
if "_type" not in obj:
|
||||
return obj
|
||||
_type = obj.pop('_type')
|
||||
if _type == 'Resource':
|
||||
_type = obj.pop("_type")
|
||||
if _type == "Resource":
|
||||
return kubernetes.dynamic.Resource(client=self.client, **obj)
|
||||
elif _type == 'ResourceList':
|
||||
elif _type == "ResourceList":
|
||||
return ResourceList(self.client, **obj)
|
||||
elif _type == 'ResourceGroup':
|
||||
return kubernetes.dynamic.discovery.ResourceGroup(obj['preferred'], resources=self.object_hook(obj['resources']))
|
||||
elif _type == "ResourceGroup":
|
||||
return kubernetes.dynamic.discovery.ResourceGroup(
|
||||
obj["preferred"], resources=self.object_hook(obj["resources"])
|
||||
)
|
||||
return obj
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
@@ -21,11 +22,19 @@ import kubernetes.dynamic
|
||||
|
||||
|
||||
class ResourceList(kubernetes.dynamic.resource.ResourceList):
|
||||
def __init__(self, client, group='', api_version='v1', base_kind='', kind=None, base_resource_lookup=None):
|
||||
def __init__(
|
||||
self,
|
||||
client,
|
||||
group="",
|
||||
api_version="v1",
|
||||
base_kind="",
|
||||
kind=None,
|
||||
base_resource_lookup=None,
|
||||
):
|
||||
self.client = client
|
||||
self.group = group
|
||||
self.api_version = api_version
|
||||
self.kind = kind or '{0}List'.format(base_kind)
|
||||
self.kind = kind or "{0}List".format(base_kind)
|
||||
self.base_kind = base_kind
|
||||
self.base_resource_lookup = base_resource_lookup
|
||||
self.__base_resource = None
|
||||
@@ -34,16 +43,18 @@ class ResourceList(kubernetes.dynamic.resource.ResourceList):
|
||||
if self.__base_resource:
|
||||
return self.__base_resource
|
||||
elif self.base_resource_lookup:
|
||||
self.__base_resource = self.client.resources.get(**self.base_resource_lookup)
|
||||
self.__base_resource = self.client.resources.get(
|
||||
**self.base_resource_lookup
|
||||
)
|
||||
return self.__base_resource
|
||||
return None
|
||||
|
||||
def to_dict(self):
|
||||
return {
|
||||
'_type': 'ResourceList',
|
||||
'group': self.group,
|
||||
'api_version': self.api_version,
|
||||
'kind': self.kind,
|
||||
'base_kind': self.base_kind,
|
||||
'base_resource_lookup': self.base_resource_lookup
|
||||
"_type": "ResourceList",
|
||||
"group": self.group,
|
||||
"api_version": self.api_version,
|
||||
"kind": self.kind,
|
||||
"base_kind": self.base_kind,
|
||||
"base_resource_lookup": self.base_resource_lookup,
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -14,6 +14,7 @@
|
||||
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
|
||||
@@ -15,7 +15,8 @@
|
||||
# Implement ConfigMapHash and SecretHash equivalents
|
||||
# Based on https://github.com/kubernetes/kubernetes/pull/49961
|
||||
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
from __future__ import absolute_import, division, print_function
|
||||
|
||||
__metaclass__ = type
|
||||
|
||||
import json
|
||||
@@ -23,6 +24,7 @@ import hashlib
|
||||
|
||||
try:
|
||||
import string
|
||||
|
||||
maketrans = string.maketrans
|
||||
except AttributeError:
|
||||
maketrans = str.maketrans
|
||||
@@ -44,21 +46,21 @@ def sorted_dict(unsorted_dict):
|
||||
|
||||
def generate_hash(resource):
|
||||
# Get name from metadata
|
||||
metada = resource.get('metadata', {})
|
||||
key = 'name'
|
||||
resource['name'] = metada.get('name', '')
|
||||
generate_name = metada.get('generateName', '')
|
||||
if resource['name'] == '' and generate_name:
|
||||
del(resource['name'])
|
||||
key = 'generateName'
|
||||
resource['generateName'] = generate_name
|
||||
if resource['kind'] == 'ConfigMap':
|
||||
marshalled = marshal(sorted_dict(resource), ['data', 'kind', key])
|
||||
del(resource[key])
|
||||
metada = resource.get("metadata", {})
|
||||
key = "name"
|
||||
resource["name"] = metada.get("name", "")
|
||||
generate_name = metada.get("generateName", "")
|
||||
if resource["name"] == "" and generate_name:
|
||||
del resource["name"]
|
||||
key = "generateName"
|
||||
resource["generateName"] = generate_name
|
||||
if resource["kind"] == "ConfigMap":
|
||||
marshalled = marshal(sorted_dict(resource), ["data", "kind", key])
|
||||
del resource[key]
|
||||
return encode(marshalled)
|
||||
if resource['kind'] == 'Secret':
|
||||
marshalled = marshal(sorted_dict(resource), ['data', 'kind', key, 'type'])
|
||||
del(resource[key])
|
||||
if resource["kind"] == "Secret":
|
||||
marshalled = marshal(sorted_dict(resource), ["data", "kind", key, "type"])
|
||||
del resource[key]
|
||||
return encode(marshalled)
|
||||
raise NotImplementedError
|
||||
|
||||
@@ -67,8 +69,10 @@ def marshal(data, keys):
|
||||
ordered = OrderedDict()
|
||||
for key in keys:
|
||||
ordered[key] = data.get(key, "")
|
||||
return json.dumps(ordered, separators=(',', ':')).encode('utf-8')
|
||||
return json.dumps(ordered, separators=(",", ":")).encode("utf-8")
|
||||
|
||||
|
||||
def encode(resource):
|
||||
return hashlib.sha256(resource).hexdigest()[:10].translate(maketrans("013ae", "ghkmt"))
|
||||
return (
|
||||
hashlib.sha256(resource).hexdigest()[:10].translate(maketrans("013ae", "ghkmt"))
|
||||
)
|
||||
|
||||
@@ -18,6 +18,7 @@ from ansible.module_utils.basic import missing_required_lib
|
||||
|
||||
try:
|
||||
import yaml
|
||||
|
||||
HAS_YAML = True
|
||||
except ImportError:
|
||||
YAML_IMP_ERR = traceback.format_exc()
|
||||
@@ -28,11 +29,11 @@ except ImportError:
|
||||
def prepare_helm_environ_update(module):
|
||||
environ_update = {}
|
||||
file_to_cleam_up = None
|
||||
kubeconfig_path = module.params.get('kubeconfig')
|
||||
if module.params.get('context') is not None:
|
||||
environ_update["HELM_KUBECONTEXT"] = module.params.get('context')
|
||||
if module.params.get('release_namespace'):
|
||||
environ_update["HELM_NAMESPACE"] = module.params.get('release_namespace')
|
||||
kubeconfig_path = module.params.get("kubeconfig")
|
||||
if module.params.get("context") is not None:
|
||||
environ_update["HELM_KUBECONTEXT"] = module.params.get("context")
|
||||
if module.params.get("release_namespace"):
|
||||
environ_update["HELM_NAMESPACE"] = module.params.get("release_namespace")
|
||||
if module.params.get("api_key"):
|
||||
environ_update["HELM_KUBETOKEN"] = module.params["api_key"]
|
||||
if module.params.get("host"):
|
||||
@@ -41,7 +42,8 @@ def prepare_helm_environ_update(module):
|
||||
kubeconfig_path = write_temp_kubeconfig(
|
||||
module.params["host"],
|
||||
validate_certs=module.params["validate_certs"],
|
||||
ca_cert=module.params["ca_cert"])
|
||||
ca_cert=module.params["ca_cert"],
|
||||
)
|
||||
file_to_cleam_up = kubeconfig_path
|
||||
if kubeconfig_path is not None:
|
||||
environ_update["KUBECONFIG"] = kubeconfig_path
|
||||
@@ -61,7 +63,9 @@ def run_helm(module, command, fails_on_error=True):
|
||||
rc, out, err = module.run_command(command, environ_update=environ_update)
|
||||
if fails_on_error and rc != 0:
|
||||
module.fail_json(
|
||||
msg="Failure when executing Helm command. Exited {0}.\nstdout: {1}\nstderr: {2}".format(rc, out, err),
|
||||
msg="Failure when executing Helm command. Exited {0}.\nstdout: {1}\nstderr: {2}".format(
|
||||
rc, out, err
|
||||
),
|
||||
stdout=out,
|
||||
stderr=err,
|
||||
command=command,
|
||||
@@ -90,23 +94,11 @@ def write_temp_kubeconfig(server, validate_certs=True, ca_cert=None):
|
||||
content = {
|
||||
"apiVersion": "v1",
|
||||
"kind": "Config",
|
||||
"clusters": [
|
||||
{
|
||||
"cluster": {
|
||||
"server": server,
|
||||
},
|
||||
"name": "generated-cluster"
|
||||
}
|
||||
],
|
||||
"clusters": [{"cluster": {"server": server}, "name": "generated-cluster"}],
|
||||
"contexts": [
|
||||
{
|
||||
"context": {
|
||||
"cluster": "generated-cluster"
|
||||
},
|
||||
"name": "generated-context"
|
||||
}
|
||||
{"context": {"cluster": "generated-cluster"}, "name": "generated-context"}
|
||||
],
|
||||
"current-context": "generated-context"
|
||||
"current-context": "generated-context",
|
||||
}
|
||||
|
||||
if not validate_certs:
|
||||
@@ -115,7 +107,7 @@ def write_temp_kubeconfig(server, validate_certs=True, ca_cert=None):
|
||||
content["clusters"][0]["cluster"]["certificate-authority"] = ca_cert
|
||||
|
||||
_fd, file_name = tempfile.mkstemp()
|
||||
with os.fdopen(_fd, 'w') as fp:
|
||||
with os.fdopen(_fd, "w") as fp:
|
||||
yaml.dump(content, fp)
|
||||
return file_name
|
||||
|
||||
@@ -128,7 +120,7 @@ def get_helm_plugin_list(module, helm_bin=None):
|
||||
return []
|
||||
helm_plugin_list = helm_bin + " list"
|
||||
rc, out, err = run_helm(module, helm_plugin_list)
|
||||
if rc != 0 or (out == '' and err == ''):
|
||||
if rc != 0 or (out == "" and err == ""):
|
||||
module.fail_json(
|
||||
msg="Failed to get Helm plugin info",
|
||||
command=helm_plugin_list,
|
||||
@@ -150,11 +142,11 @@ def parse_helm_plugin_list(module, output=None):
|
||||
for line in output:
|
||||
if line.startswith("NAME"):
|
||||
continue
|
||||
name, version, description = line.split('\t', 3)
|
||||
name, version, description = line.split("\t", 3)
|
||||
name = name.strip()
|
||||
version = version.strip()
|
||||
description = description.strip()
|
||||
if name == '':
|
||||
if name == "":
|
||||
continue
|
||||
ret.append((name, version, description))
|
||||
|
||||
|
||||
@@ -14,26 +14,37 @@
|
||||
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
from kubernetes.dynamic import DynamicClient
|
||||
|
||||
from ansible_collections.kubernetes.core.plugins.module_utils.apply import k8s_apply
|
||||
from ansible_collections.kubernetes.core.plugins.module_utils.exceptions import ApplyException
|
||||
from ansible_collections.kubernetes.core.plugins.module_utils.exceptions import (
|
||||
ApplyException,
|
||||
)
|
||||
|
||||
|
||||
class K8SDynamicClient(DynamicClient):
|
||||
def apply(self, resource, body=None, name=None, namespace=None, **kwargs):
|
||||
body = super().serialize_body(body)
|
||||
body['metadata'] = body.get('metadata', dict())
|
||||
name = name or body['metadata'].get('name')
|
||||
body["metadata"] = body.get("metadata", dict())
|
||||
name = name or body["metadata"].get("name")
|
||||
if not name:
|
||||
raise ValueError("name is required to apply {0}.{1}".format(resource.group_version, resource.kind))
|
||||
raise ValueError(
|
||||
"name is required to apply {0}.{1}".format(
|
||||
resource.group_version, resource.kind
|
||||
)
|
||||
)
|
||||
if resource.namespaced:
|
||||
body['metadata']['namespace'] = super().ensure_namespace(resource, namespace, body)
|
||||
body["metadata"]["namespace"] = super().ensure_namespace(
|
||||
resource, namespace, body
|
||||
)
|
||||
try:
|
||||
return k8s_apply(resource, body, **kwargs)
|
||||
except ApplyException as e:
|
||||
raise ValueError("Could not apply strategic merge to %s/%s: %s" %
|
||||
(body['kind'], body['metadata']['name'], e))
|
||||
raise ValueError(
|
||||
"Could not apply strategic merge to %s/%s: %s"
|
||||
% (body["kind"], body["metadata"]["name"], e)
|
||||
)
|
||||
|
||||
@@ -17,7 +17,7 @@ import re
|
||||
|
||||
class Selector(object):
|
||||
|
||||
equality_based_operators = ('==', '!=', '=')
|
||||
equality_based_operators = ("==", "!=", "=")
|
||||
|
||||
def __init__(self, data):
|
||||
self._operator = None
|
||||
@@ -27,18 +27,23 @@ class Selector(object):
|
||||
for op in self.equality_based_operators:
|
||||
idx = no_whitespace_data.find(op)
|
||||
if idx != -1:
|
||||
self._operator = "in" if op == '==' or op == '=' else "notin"
|
||||
self._operator = "in" if op == "==" or op == "=" else "notin"
|
||||
self._key = no_whitespace_data[0:idx]
|
||||
# fmt: off
|
||||
self._data = [no_whitespace_data[idx + len(op):]]
|
||||
# fmt: on
|
||||
break
|
||||
|
||||
def parse_set_based_requirement(self, data):
|
||||
m = re.match(r'( *)([a-z0-9A-Z][a-z0-9A-Z\._-]*[a-z0-9A-Z])( +)(notin|in)( +)\((.*)\)( *)', data)
|
||||
m = re.match(
|
||||
r"( *)([a-z0-9A-Z][a-z0-9A-Z\._-]*[a-z0-9A-Z])( +)(notin|in)( +)\((.*)\)( *)",
|
||||
data,
|
||||
)
|
||||
if m:
|
||||
self._set_based_requirement = True
|
||||
self._key = m.group(2)
|
||||
self._operator = m.group(4)
|
||||
self._data = [x.replace(' ', '') for x in m.group(6).split(',') if x != '']
|
||||
self._data = [x.replace(" ", "") for x in m.group(6).split(",") if x != ""]
|
||||
return True
|
||||
elif all(x not in data for x in self.equality_based_operators):
|
||||
self._key = data.rstrip(" ").lstrip(" ")
|
||||
@@ -54,18 +59,21 @@ class Selector(object):
|
||||
elif self._operator == "notin":
|
||||
return self._key not in labels or labels.get(self._key) not in self._data
|
||||
else:
|
||||
return self._key not in labels if self._operator == "!" else self._key in labels
|
||||
return (
|
||||
self._key not in labels
|
||||
if self._operator == "!"
|
||||
else self._key in labels
|
||||
)
|
||||
|
||||
|
||||
class LabelSelectorFilter(object):
|
||||
|
||||
def __init__(self, label_selectors):
|
||||
self.selectors = [Selector(data) for data in label_selectors]
|
||||
|
||||
def isMatching(self, definition):
|
||||
if "metadata" not in definition or "labels" not in definition['metadata']:
|
||||
if "metadata" not in definition or "labels" not in definition["metadata"]:
|
||||
return False
|
||||
labels = definition['metadata']['labels']
|
||||
labels = definition["metadata"]["labels"]
|
||||
if not isinstance(labels, dict):
|
||||
return None
|
||||
return all(sel.isMatch(labels) for sel in self.selectors)
|
||||
|
||||
Reference in New Issue
Block a user