mirror of
https://github.com/ansible-collections/kubernetes.core.git
synced 2026-03-27 05:43:02 +00:00
k8s - add support for Server Side apply (#260)
k8s - add support for Server Side apply SUMMARY Server side apply is now support for k8s module with this Pull request. The feature is not yet released on kubernetes-client, once this is done, we can merge this pull request. closes #87 ISSUE TYPE Feature Pull Request COMPONENT NAME k8s ADDITIONAL INFORMATION Reviewed-by: Mike Graves <mgraves@redhat.com> Reviewed-by: None <None> Reviewed-by: None <None>
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
minor_changes:
|
||||
- k8s - add support for server_side_apply. (https://github.com/ansible-collections/kubernetes.core/issues/87).
|
||||
@@ -896,6 +896,139 @@
|
||||
that:
|
||||
- k8s_networkpolicy is not changed
|
||||
|
||||
# Server Side Apply
|
||||
- name: Create Configmap using server side apply - field_manager not specified
|
||||
k8s:
|
||||
namespace: "{{ apply_namespace }}"
|
||||
definition:
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: server-side-cm
|
||||
data:
|
||||
key: value-0
|
||||
apply: yes
|
||||
server_side_apply:
|
||||
force_conflicts: false
|
||||
register: result
|
||||
ignore_errors: true
|
||||
|
||||
- name: Check that configmap creation failed
|
||||
assert:
|
||||
that:
|
||||
- result is failed
|
||||
- '"field_manager" in result.msg'
|
||||
|
||||
- name: Create Configmap using server side apply
|
||||
k8s:
|
||||
namespace: "{{ apply_namespace }}"
|
||||
definition:
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: server-side-cm
|
||||
data:
|
||||
key: value-0
|
||||
apply: yes
|
||||
server_side_apply:
|
||||
field_manager: "manager-00"
|
||||
register: result
|
||||
|
||||
- name: Check configmap was created with expected manager
|
||||
assert:
|
||||
that:
|
||||
- result is changed
|
||||
- result.result.metadata.managedFields | length == 1
|
||||
- result.result.metadata.managedFields[0].manager == 'manager-00'
|
||||
|
||||
- name: Apply ConfigMap using same parameters
|
||||
k8s:
|
||||
namespace: "{{ apply_namespace }}"
|
||||
definition:
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: server-side-cm
|
||||
data:
|
||||
key: value-0
|
||||
apply: yes
|
||||
server_side_apply:
|
||||
field_manager: "manager-00"
|
||||
register: result
|
||||
|
||||
- name: Assert that nothing change using check_mode
|
||||
assert:
|
||||
that:
|
||||
- result is not changed
|
||||
|
||||
- name: Apply ConfigMap adding new manager
|
||||
k8s:
|
||||
namespace: "{{ apply_namespace }}"
|
||||
definition:
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: server-side-cm
|
||||
data:
|
||||
key: value-0
|
||||
apply: yes
|
||||
server_side_apply:
|
||||
field_manager: "manager-01"
|
||||
register: result
|
||||
|
||||
- name: Assert that number of manager has increased
|
||||
assert:
|
||||
that:
|
||||
- result is changed
|
||||
- result.result.metadata.managedFields | length == 2
|
||||
|
||||
- name: Apply changes to Configmap using new field_manager
|
||||
k8s:
|
||||
namespace: "{{ apply_namespace }}"
|
||||
definition:
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: server-side-cm
|
||||
data:
|
||||
key: value-1
|
||||
apply: yes
|
||||
server_side_apply:
|
||||
field_manager: "manager-02"
|
||||
register: result
|
||||
ignore_errors: true
|
||||
|
||||
- name: assert that operation failed with conflicts
|
||||
assert:
|
||||
that:
|
||||
- result is failed
|
||||
- result.reason == 'Conflict'
|
||||
|
||||
- name: Apply changes to Configmap using new field_manager and force_conflicts
|
||||
k8s:
|
||||
namespace: "{{ apply_namespace }}"
|
||||
definition:
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: server-side-cm
|
||||
data:
|
||||
key: value-1
|
||||
apply: yes
|
||||
server_side_apply:
|
||||
field_manager: "manager-02"
|
||||
force_conflicts: true
|
||||
register: result
|
||||
|
||||
- name: assert that operation failed with conflicts
|
||||
assert:
|
||||
that:
|
||||
- result is changed
|
||||
- result.result.metadata.managedFields | length == 1
|
||||
- result.result.metadata.managedFields[0].manager == 'manager-02'
|
||||
- result.result.data.key == 'value-1'
|
||||
|
||||
|
||||
always:
|
||||
- name: Remove namespace
|
||||
k8s:
|
||||
|
||||
@@ -114,12 +114,14 @@ def apply_patch(actual, desired):
|
||||
return actual, dict_merge(desired, annotate(desired))
|
||||
|
||||
|
||||
def apply_object(resource, definition):
|
||||
def apply_object(resource, definition, server_side=False):
|
||||
try:
|
||||
actual = resource.get(
|
||||
name=definition["metadata"]["name"],
|
||||
namespace=definition["metadata"].get("namespace"),
|
||||
)
|
||||
if server_side:
|
||||
return actual, None
|
||||
except NotFoundError:
|
||||
return None, dict_merge(definition, annotate(definition))
|
||||
return apply_patch(actual.to_dict(), definition)
|
||||
@@ -127,6 +129,17 @@ def apply_object(resource, definition):
|
||||
|
||||
def k8s_apply(resource, definition, **kwargs):
|
||||
existing, desired = apply_object(resource, definition)
|
||||
server_side = kwargs.get("server_side", False)
|
||||
if server_side:
|
||||
body = json.dumps(definition).encode()
|
||||
# server_side_apply is forces content_type to 'application/apply-patch+yaml'
|
||||
return resource.server_side_apply(
|
||||
body=body,
|
||||
name=definition["metadata"]["name"],
|
||||
namespace=definition["metadata"].get("namespace"),
|
||||
force_conflicts=kwargs.get("force_conflicts"),
|
||||
field_manager=kwargs.get("field_manager"),
|
||||
)
|
||||
if not existing:
|
||||
return resource.create(
|
||||
body=desired, namespace=definition["metadata"].get("namespace"), **kwargs
|
||||
|
||||
@@ -490,7 +490,7 @@ class K8sAnsibleMixin(object):
|
||||
self.params.pop(alias)
|
||||
|
||||
def load_resource_definitions(self, src):
|
||||
""" Load the requested src path """
|
||||
"""Load the requested src path"""
|
||||
result = None
|
||||
path = os.path.normpath(src)
|
||||
if not os.path.exists(path):
|
||||
@@ -834,6 +834,7 @@ class K8sAnsibleMixin(object):
|
||||
wait_condition = None
|
||||
continue_on_error = self.params.get("continue_on_error")
|
||||
label_selectors = self.params.get("label_selectors")
|
||||
server_side_apply = self.params.get("server_side_apply")
|
||||
if self.params.get("wait_condition") and self.params["wait_condition"].get(
|
||||
"type"
|
||||
):
|
||||
@@ -1028,6 +1029,7 @@ class K8sAnsibleMixin(object):
|
||||
namespace=namespace,
|
||||
)
|
||||
)
|
||||
|
||||
return result
|
||||
if apply:
|
||||
if self.check_mode and not self.supports_dry_run:
|
||||
@@ -1043,6 +1045,24 @@ class K8sAnsibleMixin(object):
|
||||
params = {}
|
||||
if self.check_mode:
|
||||
params["dry_run"] = "All"
|
||||
if server_side_apply:
|
||||
params["server_side"] = True
|
||||
if LooseVersion(kubernetes.__version__) < LooseVersion(
|
||||
"19.15.0"
|
||||
):
|
||||
msg = "kubernetes >= 19.15.0 is required to use server side apply."
|
||||
if continue_on_error:
|
||||
result["error"] = dict(msg=msg)
|
||||
return result
|
||||
else:
|
||||
self.fail_json(
|
||||
msg=msg, version=kubernetes.__version__
|
||||
)
|
||||
if not server_side_apply.get("field_manager"):
|
||||
self.fail(
|
||||
msg="field_manager is required to use server side apply."
|
||||
)
|
||||
params.update(server_side_apply)
|
||||
k8s_obj = resource.apply(
|
||||
definition, namespace=namespace, **params
|
||||
).to_dict()
|
||||
|
||||
@@ -152,6 +152,26 @@ options:
|
||||
- mutually exclusive with C(name).
|
||||
type: str
|
||||
version_added: 2.3.0
|
||||
server_side_apply:
|
||||
description:
|
||||
- When this option is set, apply runs in the server instead of the client.
|
||||
- Ignored if C(apply) is not set or is set to False.
|
||||
- This option requires "kubernetes >= 19.15.0".
|
||||
type: dict
|
||||
version_added: 2.3.0
|
||||
suboptions:
|
||||
field_manager:
|
||||
type: str
|
||||
description:
|
||||
- Name of the manager used to track field ownership.
|
||||
required: True
|
||||
force_conflicts:
|
||||
description:
|
||||
- A conflict is a special status error that occurs when an Server Side Apply operation tries to change a field,
|
||||
which another user also claims to manage.
|
||||
- When set to True, server-side apply will force the changes against conflicts.
|
||||
type: bool
|
||||
default: False
|
||||
|
||||
requirements:
|
||||
- "python >= 3.6"
|
||||
@@ -302,6 +322,19 @@ EXAMPLES = r"""
|
||||
- name: py
|
||||
image: python:3.7-alpine
|
||||
imagePullPolicy: IfNotPresent
|
||||
|
||||
# Server side apply
|
||||
- name: Create configmap using server side apply
|
||||
kubernetes.core.k8s:
|
||||
namespace: testing
|
||||
definition:
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: my-configmap
|
||||
apply: yes
|
||||
server_side_apply:
|
||||
field_manager: ansible
|
||||
"""
|
||||
|
||||
RETURN = r"""
|
||||
@@ -368,6 +401,13 @@ def validate_spec():
|
||||
)
|
||||
|
||||
|
||||
def server_apply_spec():
|
||||
return dict(
|
||||
field_manager=dict(type="str", required=True),
|
||||
force_conflicts=dict(type="bool", default=False),
|
||||
)
|
||||
|
||||
|
||||
def argspec():
|
||||
argument_spec = copy.deepcopy(NAME_ARG_SPEC)
|
||||
argument_spec.update(copy.deepcopy(RESOURCE_ARG_SPEC))
|
||||
@@ -390,6 +430,9 @@ def argspec():
|
||||
argument_spec["force"] = dict(type="bool", default=False)
|
||||
argument_spec["label_selectors"] = dict(type="list", elements="str")
|
||||
argument_spec["generate_name"] = dict()
|
||||
argument_spec["server_side_apply"] = dict(
|
||||
type="dict", default=None, options=server_apply_spec()
|
||||
)
|
||||
|
||||
return argument_spec
|
||||
|
||||
|
||||
Reference in New Issue
Block a user