Skip to content

Commit

Permalink
k8s - add support for Server Side apply (#260)
Browse files Browse the repository at this point in the history
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 <[email protected]>
Reviewed-by: None <None>
Reviewed-by: None <None>
  • Loading branch information
abikouo authored Dec 16, 2021
1 parent 526f045 commit b19ff9d
Show file tree
Hide file tree
Showing 5 changed files with 213 additions and 2 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
minor_changes:
- k8s - add support for server_side_apply. (https://github.com/ansible-collections/kubernetes.core/issues/87).
133 changes: 133 additions & 0 deletions molecule/default/tasks/apply.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
15 changes: 14 additions & 1 deletion plugins/module_utils/apply.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,19 +114,32 @@ 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)


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()

This comment has been minimized.

Copy link
@flybyray

flybyray Nov 30, 2022

why encode()?
body 's type is bytes.

This comment has been minimized.

Copy link
@gravesm

gravesm Nov 30, 2022

Member

I'm not sure what you're asking. encode() converts to bytes. If you think you have found a bug, could you file a new issue?

This comment has been minimized.

Copy link
@flybyray

flybyray Nov 30, 2022

Thx you already did:
#548

# 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"),

This comment has been minimized.

Copy link
@flybyray

flybyray Nov 30, 2022

And somehow here some options like dry_run from kwargs are not passed through to the function. Hence there is this issue:
#547

)
if not existing:
return resource.create(
body=desired, namespace=definition["metadata"].get("namespace"), **kwargs
Expand Down
22 changes: 21 additions & 1 deletion plugins/module_utils/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -490,7 +490,7 @@ def remove_aliases(self):
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):
Expand Down Expand Up @@ -834,6 +834,7 @@ def perform_action(self, resource, definition):
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"
):
Expand Down Expand Up @@ -1028,6 +1029,7 @@ def _empty_resource_list():
namespace=namespace,
)
)

return result
if apply:
if self.check_mode and not self.supports_dry_run:
Expand All @@ -1043,6 +1045,24 @@ def _empty_resource_list():
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()
Expand Down
43 changes: 43 additions & 0 deletions plugins/modules/k8s.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,26 @@
- 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"
Expand Down Expand Up @@ -302,6 +322,19 @@
- 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"""
Expand Down Expand Up @@ -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))
Expand All @@ -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

Expand Down

0 comments on commit b19ff9d

Please sign in to comment.