Files
kubernetes.core/plugins/modules/k8s_json_patch.py
GomathiselviS b066a2dda3 Cleanup GitHub workflows (#655)
* Cleanup gha

* test by removing matrix excludes

* Rename sanity tests

* trigger integration tests

* Fix ansible-lint workflow

* Fix concurrency

* Add ansible-lint config

* Add ansible-lint config

* Fix integration and lint issues

* integration wf

* fix yamllint issues

* fix yamllint issues

* update readme and add ignore-2.16.txt

* fix ansible-doc

* Add version

* Use /dev/random to generate random data

The GHA environment has difficultly generating entropy. Trying to read
from /dev/urandom just blocks forever. We don't care if the random data
is cryptographically secure; it's just garbage data for the test. Read
from /dev/random, instead. This is only used during the k8s_copy test
target.

This also removes the custom test module that was being used to generate
the files. It's not worth maintaining this for two task that can be
replaced with some simple command/shell tasks.

* Fix saniry errors

* test github_action fix

* Address review comments

* Remove default types

* review comments

* isort fixes

* remove tags

* Add setuptools to venv

* Test gh changes

* update changelog

* update ignore-2.16

* Fix indentation in inventory plugin example

* Update .github/workflows/integration-tests.yaml

* Update integration-tests.yaml

---------

Co-authored-by: Mike Graves <mgraves@redhat.com>
Co-authored-by: Bikouo Aubin <79859644+abikouo@users.noreply.github.com>
2023-11-10 16:33:40 +01:00

297 lines
8.5 KiB
Python

#!/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(kubernetes.core.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
path: /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._text import to_native
from ansible.module_utils.basic import missing_required_lib
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.k8s.client import (
get_api_client,
)
from ansible_collections.kubernetes.core.plugins.module_utils.k8s.core import (
AnsibleK8SModule,
)
from ansible_collections.kubernetes.core.plugins.module_utils.k8s.exceptions import (
CoreException,
)
from ansible_collections.kubernetes.core.plugins.module_utils.k8s.service import (
diff_objects,
)
from ansible_collections.kubernetes.core.plugins.module_utils.k8s.waiter import (
get_waiter,
)
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(module, client):
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"]
def build_error_msg(kind, name, msg):
return "%s %s: %s" % (kind, name, msg)
resource = client.resource(kind, api_version)
try:
existing = client.get(resource, 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 and not client.dry_run:
obj, error = json_patch(existing.to_dict(), patch)
if error:
module.fail_json(**error)
else:
params = {}
if module.check_mode:
params["dry_run"] = "All"
try:
obj = client.patch(
resource,
patch,
name=name,
namespace=namespace,
content_type="application/json-patch+json",
**params
).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:
waiter = get_waiter(client, resource, condition=wait_condition)
success, result["result"], result["duration"] = waiter.wait(
wait_timeout, wait_sleep, name, namespace
)
match, diffs = diff_objects(existing.to_dict(), obj)
result["changed"] = not match
if module._diff:
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 = AnsibleK8SModule(
module_class=AnsibleModule, argument_spec=args, supports_check_mode=True
)
try:
client = get_api_client(module)
execute_module(module, client)
except CoreException as e:
module.fail_from_exception(e)
if __name__ == "__main__":
main()