mirror of
https://github.com/ansible-collections/kubernetes.core.git
synced 2026-03-26 21:33:02 +00:00
Split JSON patch out into a new module (#99)
Co-authored-by: Abhijeet Kasurde <akasurde@redhat.com>
This commit is contained in:
280
plugins/modules/k8s_json_patch.py
Normal file
280
plugins/modules/k8s_json_patch.py
Normal file
@@ -0,0 +1,280 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright: (C), 2018 Red Hat | Ansible
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
module: k8s_json_patch
|
||||
|
||||
short_description: Apply JSON patch operations to existing objects
|
||||
|
||||
description:
|
||||
- This module is used to apply RFC 6902 JSON patch operations only.
|
||||
- Use the M(k8s) module for strategic merge or JSON merge operations.
|
||||
- The jsonpatch library is required for check mode.
|
||||
|
||||
version_added: 2.0.0
|
||||
|
||||
author:
|
||||
- Mike Graves (@gravesm)
|
||||
|
||||
options:
|
||||
api_version:
|
||||
description:
|
||||
- Use to specify the API version.
|
||||
- Use in conjunction with I(kind), I(name), and I(namespace) to identify a specific object.
|
||||
type: str
|
||||
default: v1
|
||||
aliases:
|
||||
- api
|
||||
- version
|
||||
kind:
|
||||
description:
|
||||
- Use to specify an object model.
|
||||
- Use in conjunction with I(api_version), I(name), and I(namespace) to identify a specific object.
|
||||
type: str
|
||||
required: yes
|
||||
namespace:
|
||||
description:
|
||||
- Use to specify an object namespace.
|
||||
- Use in conjunction with I(api_version), I(kind), and I(name) to identify a specific object.
|
||||
type: str
|
||||
name:
|
||||
description:
|
||||
- Use to specify an object name.
|
||||
- Use in conjunction with I(api_version), I(kind), and I(namespace) to identify a specific object.
|
||||
type: str
|
||||
required: yes
|
||||
patch:
|
||||
description:
|
||||
- List of JSON patch operations.
|
||||
required: yes
|
||||
type: list
|
||||
elements: dict
|
||||
|
||||
extends_documentation_fragment:
|
||||
- kubernetes.core.k8s_auth_options
|
||||
- kubernetes.core.k8s_wait_options
|
||||
|
||||
requirements:
|
||||
- "python >= 3.6"
|
||||
- "kubernetes >= 12.0.0"
|
||||
- "PyYAML >= 3.11"
|
||||
- "jsonpatch"
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
- name: Apply multiple patch operations to an existing Pod
|
||||
kubernetes.core.k8s_json_patch:
|
||||
kind: Pod
|
||||
namespace: testing
|
||||
name: mypod
|
||||
patch:
|
||||
- op: add
|
||||
path: /metadata/labels/app
|
||||
value: myapp
|
||||
- op: replace
|
||||
patch: /spec/containers/0/image
|
||||
value: nginx
|
||||
'''
|
||||
|
||||
RETURN = r'''
|
||||
result:
|
||||
description: The modified object.
|
||||
returned: success
|
||||
type: dict
|
||||
contains:
|
||||
api_version:
|
||||
description: The versioned schema of this representation of an object.
|
||||
returned: success
|
||||
type: str
|
||||
kind:
|
||||
description: The REST resource this object represents.
|
||||
returned: success
|
||||
type: str
|
||||
metadata:
|
||||
description: Standard object metadata. Includes name, namespace, annotations, labels, etc.
|
||||
returned: success
|
||||
type: dict
|
||||
spec:
|
||||
description: Specific attributes of the object. Will vary based on the I(api_version) and I(kind).
|
||||
returned: success
|
||||
type: dict
|
||||
status:
|
||||
description: Current status details for the object.
|
||||
returned: success
|
||||
type: dict
|
||||
duration:
|
||||
description: Elapsed time of task in seconds.
|
||||
returned: when C(wait) is true
|
||||
type: int
|
||||
sample: 48
|
||||
error:
|
||||
description: The error when patching the object.
|
||||
returned: error
|
||||
type: dict
|
||||
sample: {
|
||||
"msg": "Failed to import the required Python library (jsonpatch) ...",
|
||||
"exception": "Traceback (most recent call last): ..."
|
||||
}
|
||||
'''
|
||||
|
||||
import copy
|
||||
import traceback
|
||||
|
||||
from ansible.module_utils.basic import missing_required_lib
|
||||
from ansible.module_utils._text import to_native
|
||||
from ansible_collections.kubernetes.core.plugins.module_utils.ansiblemodule import AnsibleModule
|
||||
from ansible_collections.kubernetes.core.plugins.module_utils.args_common import AUTH_ARG_SPEC, WAIT_ARG_SPEC
|
||||
from ansible_collections.kubernetes.core.plugins.module_utils.common import (
|
||||
get_api_client, K8sAnsibleMixin)
|
||||
|
||||
try:
|
||||
from kubernetes.dynamic.exceptions import DynamicApiError
|
||||
except ImportError:
|
||||
# kubernetes library check happens in common.py
|
||||
pass
|
||||
|
||||
JSON_PATCH_IMPORT_ERR = None
|
||||
try:
|
||||
import jsonpatch
|
||||
HAS_JSON_PATCH = True
|
||||
except ImportError:
|
||||
HAS_JSON_PATCH = False
|
||||
JSON_PATCH_IMPORT_ERR = traceback.format_exc()
|
||||
|
||||
|
||||
JSON_PATCH_ARGS = {
|
||||
'api_version': {
|
||||
'default': 'v1',
|
||||
'aliases': ['api', 'version'],
|
||||
},
|
||||
"kind": {
|
||||
"type": "str",
|
||||
"required": True,
|
||||
},
|
||||
"namespace": {
|
||||
"type": "str",
|
||||
},
|
||||
"name": {
|
||||
"type": "str",
|
||||
"required": True,
|
||||
},
|
||||
"patch": {
|
||||
"type": "list",
|
||||
"required": True,
|
||||
"elements": "dict",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def json_patch(existing, patch):
|
||||
if not HAS_JSON_PATCH:
|
||||
error = {
|
||||
"msg": missing_required_lib('jsonpatch'),
|
||||
"exception": JSON_PATCH_IMPORT_ERR,
|
||||
}
|
||||
return None, error
|
||||
try:
|
||||
patch = jsonpatch.JsonPatch(patch)
|
||||
patched = patch.apply(existing)
|
||||
return patched, None
|
||||
except jsonpatch.InvalidJsonPatch as e:
|
||||
error = {
|
||||
"msg": "Invalid JSON patch",
|
||||
"exception": e
|
||||
}
|
||||
return None, error
|
||||
except jsonpatch.JsonPatchConflict as e:
|
||||
error = {
|
||||
"msg": "Patch could not be applied due to a conflict",
|
||||
"exception": e
|
||||
}
|
||||
return None, error
|
||||
|
||||
|
||||
def execute_module(k8s_module, module):
|
||||
kind = module.params.get("kind")
|
||||
api_version = module.params.get("api_version")
|
||||
name = module.params.get("name")
|
||||
namespace = module.params.get("namespace")
|
||||
patch = module.params.get("patch")
|
||||
|
||||
wait = module.params.get("wait")
|
||||
wait_sleep = module.params.get("wait_sleep")
|
||||
wait_timeout = module.params.get("wait_timeout")
|
||||
wait_condition = None
|
||||
if module.params.get("wait_condition") and module.params.get("wait_condition").get("type"):
|
||||
wait_condition = module.params['wait_condition']
|
||||
# definition is needed for wait
|
||||
definition = {
|
||||
"kind": kind,
|
||||
"metadata": {
|
||||
"name": name,
|
||||
"namespace": namespace,
|
||||
}
|
||||
}
|
||||
|
||||
def build_error_msg(kind, name, msg):
|
||||
return "%s %s: %s" % (kind, name, msg)
|
||||
|
||||
resource = k8s_module.find_resource(kind, api_version, fail=True)
|
||||
|
||||
try:
|
||||
existing = resource.get(name=name, namespace=namespace)
|
||||
except DynamicApiError as exc:
|
||||
msg = 'Failed to retrieve requested object: {0}'.format(exc.body)
|
||||
module.fail_json(msg=build_error_msg(kind, name, msg), error=exc.status, status=exc.status, reason=exc.reason)
|
||||
except ValueError as exc:
|
||||
msg = 'Failed to retrieve requested object: {0}'.format(to_native(exc))
|
||||
module.fail_json(msg=build_error_msg(kind, name, msg), error='', status='', reason='')
|
||||
|
||||
if module.check_mode:
|
||||
obj, error = json_patch(existing.to_dict(), patch)
|
||||
if error:
|
||||
module.fail_json(**error)
|
||||
else:
|
||||
try:
|
||||
obj = resource.patch(patch, name=name, namespace=namespace, content_type="application/json-patch+json").to_dict()
|
||||
except DynamicApiError as exc:
|
||||
msg = "Failed to patch existing object: {0}".format(exc.body)
|
||||
module.fail_json(msg=msg, error=exc.status, status=exc.status, reason=exc.reason)
|
||||
except Exception as exc:
|
||||
msg = "Failed to patch existing object: {0}".format(exc)
|
||||
module.fail_json(msg=msg, error=to_native(exc), status='', reason='')
|
||||
|
||||
success = True
|
||||
result = {"result": obj}
|
||||
if wait and not module.check_mode:
|
||||
success, result['result'], result['duration'] = k8s_module.wait(resource, definition, wait_sleep, wait_timeout, condition=wait_condition)
|
||||
match, diffs = k8s_module.diff_objects(existing.to_dict(), obj)
|
||||
result["changed"] = not match
|
||||
result["diff"] = diffs
|
||||
|
||||
if not success:
|
||||
msg = "Resource update timed out"
|
||||
module.fail_json(msg=msg, **result)
|
||||
|
||||
module.exit_json(**result)
|
||||
|
||||
|
||||
def main():
|
||||
args = copy.deepcopy(AUTH_ARG_SPEC)
|
||||
args.update(copy.deepcopy(WAIT_ARG_SPEC))
|
||||
args.update(JSON_PATCH_ARGS)
|
||||
module = AnsibleModule(argument_spec=args, supports_check_mode=True)
|
||||
k8s_module = K8sAnsibleMixin(module)
|
||||
k8s_module.params = module.params
|
||||
k8s_module.check_library_version()
|
||||
client = get_api_client(module)
|
||||
k8s_module.client = client
|
||||
execute_module(k8s_module, module)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user