From 192cae1507b78fa1dd9a3e4db618aae8e1042f4a Mon Sep 17 00:00:00 2001 From: abikouo <79859644+abikouo@users.noreply.github.com> Date: Wed, 19 May 2021 11:03:11 +0200 Subject: [PATCH 1/2] k8s - patch existing resource (#90) * patch only * add changelogs * Rename 89-k8s-add-parameter-patch_only.yml to 90-k8s-add-parameter-patch_only.yml * Update 90-k8s-add-parameter-patch_only.yml * patch_only parameter changed to state=patched * Update 90-k8s-add-parameter-patch_only.yml * Update plugins/modules/k8s.py Co-authored-by: Fabian von Feilitzsch * Update molecule/default/tasks/patched.yml Co-authored-by: John Mazzitelli * Update molecule/default/tasks/patched.yml Co-authored-by: John Mazzitelli * sanity issue Co-authored-by: Fabian von Feilitzsch Co-authored-by: John Mazzitelli --- .../90-k8s-add-parameter-patch_only.yml | 3 + molecule/default/converge.yml | 8 ++ molecule/default/tasks/patched.yml | 123 ++++++++++++++++++ plugins/module_utils/common.py | 15 ++- plugins/modules/k8s.py | 36 ++++- 5 files changed, 178 insertions(+), 7 deletions(-) create mode 100644 changelogs/fragments/90-k8s-add-parameter-patch_only.yml create mode 100644 molecule/default/tasks/patched.yml diff --git a/changelogs/fragments/90-k8s-add-parameter-patch_only.yml b/changelogs/fragments/90-k8s-add-parameter-patch_only.yml new file mode 100644 index 00000000..ed58b20b --- /dev/null +++ b/changelogs/fragments/90-k8s-add-parameter-patch_only.yml @@ -0,0 +1,3 @@ +--- +minor_changes: + - k8s - support ``patched`` value for ``state`` option. patched state is an existing resource that has a given patch applied (https://github.com/ansible-collections/kubernetes.core/pull/90). diff --git a/molecule/default/converge.yml b/molecule/default/converge.yml index 8a2d2f57..a2e7bfb0 100644 --- a/molecule/default/converge.yml +++ b/molecule/default/converge.yml @@ -141,6 +141,14 @@ tags: - always + - name: Include patched.yml + include_tasks: + file: tasks/patched.yml + apply: + tags: [ patched, k8s ] + tags: + - always + roles: - role: helm tags: diff --git a/molecule/default/tasks/patched.yml b/molecule/default/tasks/patched.yml new file mode 100644 index 00000000..9c0f9077 --- /dev/null +++ b/molecule/default/tasks/patched.yml @@ -0,0 +1,123 @@ +--- +- block: + - set_fact: + patch_only_namespace: + first: patched-namespace-1 + second: patched-namespace-2 + + - name: Ensure namespace {{ patch_only_namespace.first }} exist + kubernetes.core.k8s: + definition: + apiVersion: v1 + kind: Namespace + metadata: + name: "{{ patch_only_namespace.first }}" + labels: + existingLabel: "labelValue" + annotations: + existingAnnotation: "annotationValue" + wait: yes + + - name: Ensure namespace {{ patch_only_namespace.second }} does not exist + kubernetes.core.k8s_info: + kind: namespace + name: "{{ patch_only_namespace.second }}" + register: second_namespace + + - name: assert that second namespace does not exist + assert: + that: + - second_namespace.resources | length == 0 + + - name: apply patch on existing resource + kubernetes.core.k8s: + state: patched + wait: yes + definition: | + --- + apiVersion: v1 + kind: Namespace + metadata: + name: "{{ patch_only_namespace.first }}" + labels: + ansible: patched + --- + apiVersion: v1 + kind: Namespace + metadata: + name: "{{ patch_only_namespace.second }}" + labels: + ansible: patched + register: patch_resource + + - name: assert that patch succeed + assert: + that: + - patch_resource.changed + - patch_resource.result.results | selectattr('warning', 'defined') | list | length == 1 + + - name: Ensure namespace {{ patch_only_namespace.first }} was patched correctly + kubernetes.core.k8s_info: + kind: namespace + name: "{{ patch_only_namespace.first }}" + register: first_namespace + + - name: assert labels are as expected + assert: + that: + - first_namespace.resources[0].metadata.labels.ansible == "patched" + - first_namespace.resources[0].metadata.labels.existingLabel == "labelValue" + - first_namespace.resources[0].metadata.annotations.existingAnnotation == "annotationValue" + - name: Ensure namespace {{ patch_only_namespace.second }} was not created + kubernetes.core.k8s_info: + kind: namespace + name: "{{ patch_only_namespace.second }}" + register: second_namespace + + - name: assert that second namespace does not exist + assert: + that: + - second_namespace.resources | length == 0 + + - name: patch all resources (create if does not exist) + kubernetes.core.k8s: + state: present + definition: | + --- + apiVersion: v1 + kind: Namespace + metadata: + name: "{{ patch_only_namespace.first }}" + labels: + patch: ansible + --- + apiVersion: v1 + kind: Namespace + metadata: + name: "{{ patch_only_namespace.second }}" + labels: + patch: ansible + wait: yes + register: patch_resource + + - name: Ensure namespace {{ patch_only_namespace.second }} was created + kubernetes.core.k8s_info: + kind: namespace + name: "{{ patch_only_namespace.second }}" + register: second_namespace + + - name: assert that second namespace exist + assert: + that: + - second_namespace.resources | length == 1 + + always: + - name: Remove namespace + kubernetes.core.k8s: + kind: Namespace + name: "{{ item }}" + state: absent + with_items: + - "{{ patch_only_namespace.first }}" + - "{{ patch_only_namespace.second }}" + ignore_errors: true diff --git a/plugins/module_utils/common.py b/plugins/module_utils/common.py index 86b7ae9a..06f31dae 100644 --- a/plugins/module_utils/common.py +++ b/plugins/module_utils/common.py @@ -529,7 +529,8 @@ class K8sAnsibleMixin(object): if self.params['validate'] is not None: self.warnings = self.validate(definition) result = self.perform_action(resource, definition) - result['warnings'] = self.warnings + if self.warnings: + result['warnings'] = self.warnings changed = changed or result['changed'] results.append(result) @@ -670,6 +671,7 @@ class K8sAnsibleMixin(object): else: self.fail_json(msg=build_error_msg(definition['kind'], origin_name, msg), **result) return result + else: if apply: if self.check_mode: @@ -713,7 +715,14 @@ class K8sAnsibleMixin(object): return result if not existing: - if self.check_mode: + if state == 'patched': + # Silently skip this resource (do not raise an error) as 'patch_only' is set to true + result['changed'] = False + result['warning'] = "resource 'kind={kind},name={name}' was not found but will not be created as 'state'\ + parameter has been set to '{state}'".format( + kind=definition['kind'], name=origin_name, state=state) + return result + elif self.check_mode: k8s_obj = _encode_stringdata(definition) else: try: @@ -762,7 +771,7 @@ class K8sAnsibleMixin(object): match = False diffs = [] - if existing and force: + if state == 'present' and existing and force: if self.check_mode: k8s_obj = _encode_stringdata(definition) else: diff --git a/plugins/modules/k8s.py b/plugins/modules/k8s.py index f70923ff..27a5ebaf 100644 --- a/plugins/modules/k8s.py +++ b/plugins/modules/k8s.py @@ -30,7 +30,6 @@ description: - Supports check mode. extends_documentation_fragment: - - kubernetes.core.k8s_state_options - kubernetes.core.k8s_name_options - kubernetes.core.k8s_resource_options - kubernetes.core.k8s_auth_options @@ -38,6 +37,21 @@ extends_documentation_fragment: - kubernetes.core.k8s_delete_options options: + state: + description: + - Determines if an object should be created, patched, or deleted. When set to C(present), an object will be + created, if it does not already exist. If set to C(absent), an existing object will be deleted. If set to + C(present), an existing object will be patched, if its attributes differ from those specified using + I(resource_definition) or I(src). + - C(patched) state is an existing resource that has a given patch applied. If the resource doesn't exist, silently skip it (do not raise an error). + type: str + default: present + choices: [ absent, present, patched ] + force: + description: + - If set to C(yes), and I(state) is C(present), an existing object will be replaced. + type: bool + default: no merge_type: description: - Whether to override the default patch merge approach with a specific type. By default, the strategic @@ -236,6 +250,17 @@ EXAMPLES = r''' type: Progressing status: Unknown reason: DeploymentPaused + +# Patch existing namespace : add label +- name: add label to existing namespace + kubernetes.core.k8s: + state: patched + kind: Namespace + name: patch_namespace + definition: + metadata: + labels: + support: patch ''' RETURN = r''' @@ -284,7 +309,7 @@ import copy 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, NAME_ARG_SPEC, COMMON_ARG_SPEC, RESOURCE_ARG_SPEC, DELETE_OPTS_ARG_SPEC) + AUTH_ARG_SPEC, WAIT_ARG_SPEC, NAME_ARG_SPEC, RESOURCE_ARG_SPEC, DELETE_OPTS_ARG_SPEC) def validate_spec(): @@ -296,8 +321,7 @@ def validate_spec(): def argspec(): - argument_spec = copy.deepcopy(COMMON_ARG_SPEC) - argument_spec.update(copy.deepcopy(NAME_ARG_SPEC)) + argument_spec = copy.deepcopy(NAME_ARG_SPEC) argument_spec.update(copy.deepcopy(RESOURCE_ARG_SPEC)) argument_spec.update(copy.deepcopy(AUTH_ARG_SPEC)) argument_spec.update(copy.deepcopy(WAIT_ARG_SPEC)) @@ -308,6 +332,9 @@ def argspec(): argument_spec['template'] = dict(type='raw', default=None) argument_spec['delete_options'] = dict(type='dict', default=None, options=copy.deepcopy(DELETE_OPTS_ARG_SPEC)) argument_spec['continue_on_error'] = dict(type='bool', default=False) + argument_spec['state'] = dict(default='present', choices=['present', 'absent', 'patched']) + argument_spec['force'] = dict(type='bool', default=False) + return argument_spec @@ -319,6 +346,7 @@ def execute_module(module, k8s_ansible_mixin): k8s_ansible_mixin.fail_json = k8s_ansible_mixin.module.fail_json k8s_ansible_mixin.fail = k8s_ansible_mixin.module.fail_json k8s_ansible_mixin.exit_json = k8s_ansible_mixin.module.exit_json + k8s_ansible_mixin.warn = k8s_ansible_mixin.module.warn k8s_ansible_mixin.warnings = [] k8s_ansible_mixin.kind = k8s_ansible_mixin.params.get('kind') From 0bbc9ca9992c217f3cef523b99a11e3ee7c69d18 Mon Sep 17 00:00:00 2001 From: Mike Graves Date: Wed, 19 May 2021 09:29:22 -0400 Subject: [PATCH 2/2] Account for updated pods when waiting on DaemonSet (#102) * Account for updated pods when waiting on DaemonSet The exising logic that's used to determine when a DaemonSet is ready fails to account for the fact that a RollingUpdate first kills the pod and then creates a new one. Simply checking if the desiredNumberScheduled equals the numberReady will succeed in cases when the old pod takes time to shut down, and would report that the new Deployment is ready despite the fact that the old pod has not been replaced, yet. * Add changelog fragment --- .../fragments/102-wait-updated-daemonset-pods.yaml | 3 +++ molecule/default/tasks/waiter.yml | 9 +++++++++ plugins/module_utils/common.py | 1 + 3 files changed, 13 insertions(+) create mode 100644 changelogs/fragments/102-wait-updated-daemonset-pods.yaml diff --git a/changelogs/fragments/102-wait-updated-daemonset-pods.yaml b/changelogs/fragments/102-wait-updated-daemonset-pods.yaml new file mode 100644 index 00000000..2fc981de --- /dev/null +++ b/changelogs/fragments/102-wait-updated-daemonset-pods.yaml @@ -0,0 +1,3 @@ +--- +minor_changes: + - k8s - wait for all pods to update when rolling out daemonset changes (https://github.com/ansible-collections/kubernetes.core/pull/102). diff --git a/molecule/default/tasks/waiter.yml b/molecule/default/tasks/waiter.yml index 303cb2d1..44fc42b3 100644 --- a/molecule/default/tasks/waiter.yml +++ b/molecule/default/tasks/waiter.yml @@ -54,6 +54,9 @@ vars: k8s_pod_name: wait-ds k8s_pod_image: gcr.io/kuar-demo/kuard-amd64:1 + k8s_pod_command: + - sleep + - "600" register: ds - name: Check that daemonset wait worked @@ -82,6 +85,9 @@ vars: k8s_pod_name: wait-ds k8s_pod_image: gcr.io/kuar-demo/kuard-amd64:2 + k8s_pod_command: + - sleep + - "600" register: update_ds_check_mode check_mode: yes @@ -112,6 +118,9 @@ vars: k8s_pod_name: wait-ds k8s_pod_image: gcr.io/kuar-demo/kuard-amd64:3 + k8s_pod_command: + - sleep + - "600" register: ds - name: Get updated pods diff --git a/plugins/module_utils/common.py b/plugins/module_utils/common.py index 06f31dae..de3c9282 100644 --- a/plugins/module_utils/common.py +++ b/plugins/module_utils/common.py @@ -403,6 +403,7 @@ class K8sAnsibleMixin(object): def _daemonset_ready(daemonset): return (daemonset.status and daemonset.status.desiredNumberScheduled is not None + and daemonset.status.updatedNumberScheduled == daemonset.status.desiredNumberScheduled and daemonset.status.numberReady == daemonset.status.desiredNumberScheduled and daemonset.status.observedGeneration == daemonset.metadata.generation and not daemonset.status.unavailableReplicas)