Add support for configuring garbage collection (#334)

* Add support for configuring garbage collection

This surfaces deleteOptions functionality in a top-level delete_options
parameter.

* Add changelog fragment

* Remove kind and apiVersion from delete_options

* Add release version to docs
This commit is contained in:
Mike Graves
2021-01-12 14:17:18 -05:00
committed by GitHub
parent 9059f2c526
commit a9b8cc68d5
6 changed files with 298 additions and 1 deletions

View File

@@ -0,0 +1,2 @@
minor_changes:
- k8s - add a ``delete_options`` parameter to control garbage collection behavior when deleting a resource (https://github.com/ansible-collections/community.kubernetes/issues/253).

View File

@@ -30,6 +30,7 @@
- include_tasks: tasks/cluster_info.yml
- include_tasks: tasks/access_review.yml
- include_tasks: tasks/rollback.yml
- include_tasks: tasks/gc.yml
roles:
- helm

View File

@@ -0,0 +1,211 @@
---
- vars:
gc_namespace: garbage
gc_name: garbage-job
# This is a job definition that runs for 10 minutes and won't gracefully
# shutdown. It allows us to test foreground vs background deletion.
job_definition:
apiVersion: v1
kind: Job
metadata:
name: "{{ gc_name }}"
namespace: "{{ gc_namespace }}"
spec:
template:
metadata:
labels:
job: gc
spec:
containers:
- name: "{{ gc_name }}"
image: busybox
command:
- sleep
- "600"
restartPolicy: Never
block:
- name: Ensure namespace exists
k8s:
definition:
apiVersion: v1
kind: Namespace
metadata:
name: "{{ gc_namespace }}"
- name: Add a job
k8s:
definition: "{{ job_definition }}"
- name: Test that job's pod is running
k8s_info:
kind: Pod
namespace: "{{ gc_namespace }}"
label_selectors:
- "job=gc"
wait: yes
wait_timeout: 100
register: job
- name: Assert job's pod is running
assert:
that: job.resources[0].status.phase == "Running"
- name: Delete job in foreground
k8s:
kind: Job
name: "{{ gc_name }}"
namespace: "{{ gc_namespace }}"
state: absent
wait: yes
wait_timeout: 100
delete_options:
propagationPolicy: Foreground
- name: Test job's pod does not exist
k8s_info:
kind: Pod
namespace: "{{ gc_namespace }}"
label_selectors:
- "job=gc"
register: job
- name: Assert job's pod does not exist
assert:
that: not job.resources
- name: Add a job
k8s:
definition: "{{ job_definition }}"
- name: Test that job's pod is running
k8s_info:
kind: Pod
namespace: "{{ gc_namespace }}"
label_selectors:
- "job=gc"
wait: yes
wait_timeout: 100
register: job
- name: Assert job's pod is running
assert:
that: job.resources[0].status.phase == "Running"
- name: Delete job in background
k8s:
kind: Job
name: "{{ gc_name }}"
namespace: "{{ gc_namespace }}"
state: absent
wait: yes
wait_timeout: 100
delete_options:
propagationPolicy: "Background"
# The default grace period is 30s so this pod should still be running.
- name: Test job's pod exists
k8s_info:
kind: Pod
namespace: "{{ gc_namespace }}"
label_selectors:
- "job=gc"
register: job
- name: Assert job's pod still running
assert:
that: job.resources[0].status.phase == "Running"
- name: Add a job
k8s:
definition: "{{ job_definition }}"
- name: Test that job's pod is running
k8s_info:
kind: Pod
namespace: "{{ gc_namespace }}"
label_selectors:
- "job=gc"
wait: yes
wait_timeout: 100
register: job
- name: Assert job's pod is running
assert:
that: job.resources[0].status.phase == "Running"
- name: Orphan the job's pod
k8s:
kind: Job
name: "{{ gc_name }}"
namespace: "{{ gc_namespace }}"
state: absent
wait: yes
wait_timeout: 100
delete_options:
propagationPolicy: "Orphan"
- name: Ensure grace period has expired
pause:
seconds: 60
- name: Test that job's pod is still running
k8s_info:
kind: Pod
namespace: "{{ gc_namespace }}"
label_selectors:
- "job=gc"
register: job
- name: Assert job's pod is still running
assert:
that: job.resources[0].status.phase == "Running"
- name: Add a job
k8s:
definition: "{{ job_definition }}"
register: job
- name: Delete a job with failing precondition
k8s:
kind: Job
name: "{{ gc_name }}"
namespace: "{{ gc_namespace }}"
state: absent
delete_options:
preconditions:
uid: not-a-valid-uid
ignore_errors: yes
register: result
- name: Assert that deletion failed
assert:
that: result is failed
- name: Delete a job using a valid precondition
k8s:
kind: Job
name: "{{ gc_name }}"
namespace: "{{ gc_namespace }}"
state: absent
delete_options:
preconditions:
uid: "{{ job.result.metadata.uid }}"
- name: Check that job is deleted
k8s_info:
kind: Job
namespace: "{{ gc_namespace }}"
name: "{{ gc_name }}"
register: job
- name: Assert job is deleted
assert:
that: not job.resources
always:
- name: Delete namespace
k8s:
kind: Namespace
name: "{{ gc_namespace }}"
state: absent

View File

@@ -0,0 +1,51 @@
# -*- coding: utf-8 -*-
# Copyright: (c) 2020, Red Hat | Ansible
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
# Options for specifying object wait
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
class ModuleDocFragment(object):
DOCUMENTATION = r'''
options:
delete_options:
type: dict
version_added: '1.2.0'
description:
- Configure behavior when deleting an object.
- Only used when I(state=absent).
suboptions:
propagationPolicy:
type: str
description:
- Use to control how dependent objects are deleted.
- If not specified, the default policy for the object type will be used. This may vary across object types.
choices:
- "Foreground"
- "Background"
- "Orphan"
gracePeriodSeconds:
type: int
description:
- Specify how many seconds to wait before forcefully terminating.
- Only implemented for Pod resources.
- If not specified, the default grace period for the object type will be used.
preconditions:
type: dict
description:
- Specify condition that must be met for delete to proceed.
suboptions:
resourceVersion:
type: str
description:
- Specify the resource version of the target object.
uid:
type: str
description:
- Specify the UID of the target object.
'''

View File

@@ -189,6 +189,27 @@ WAIT_ARG_SPEC = dict(
)
)
DELETE_OPTS_ARG_SPEC = {
'propagationPolicy': {
'choices': ['Foreground', 'Background', 'Orphan'],
},
'gracePeriodSeconds': {
'type': 'int',
},
'preconditions': {
'type': 'dict',
'options': {
'resourceVersion': {
'type': 'str',
},
'uid': {
'type': 'str',
}
}
}
}
# Map kubernetes-client parameters to ansible parameters
AUTH_ARG_MAP = {
'kubeconfig': 'kubeconfig',
@@ -594,6 +615,7 @@ class K8sAnsibleMixin(object):
return definition
def perform_action(self, resource, definition):
delete_options = self.params.get('delete_options')
result = {'changed': False, 'result': {}}
state = self.params.get('state', None)
force = self.params.get('force', False)
@@ -646,6 +668,13 @@ class K8sAnsibleMixin(object):
# Delete the object
result['changed'] = True
if not self.check_mode:
if delete_options:
body = {
'apiVersion': 'v1',
'kind': 'DeleteOptions',
}
body.update(delete_options)
params['body'] = body
try:
k8s_obj = resource.delete(**params)
result['result'] = k8s_obj.to_dict()

View File

@@ -34,6 +34,7 @@ extends_documentation_fragment:
- community.kubernetes.k8s_resource_options
- community.kubernetes.k8s_auth_options
- community.kubernetes.k8s_wait_options
- community.kubernetes.k8s_delete_options
notes:
- If your OpenShift Python library is not 0.9.0 or newer and you are trying to
@@ -252,7 +253,8 @@ import copy
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.community.kubernetes.plugins.module_utils.common import (
K8sAnsibleMixin, COMMON_ARG_SPEC, NAME_ARG_SPEC, RESOURCE_ARG_SPEC, AUTH_ARG_SPEC, WAIT_ARG_SPEC)
K8sAnsibleMixin, COMMON_ARG_SPEC, NAME_ARG_SPEC, RESOURCE_ARG_SPEC, AUTH_ARG_SPEC,
WAIT_ARG_SPEC, DELETE_OPTS_ARG_SPEC)
class KubernetesModule(K8sAnsibleMixin):
@@ -277,6 +279,7 @@ class KubernetesModule(K8sAnsibleMixin):
argument_spec['append_hash'] = dict(type='bool', default=False)
argument_spec['apply'] = dict(type='bool', default=False)
argument_spec['template'] = dict(type='raw', default=None)
argument_spec['delete_options'] = dict(type='dict', default=None, options=copy.deepcopy(DELETE_OPTS_ARG_SPEC))
return argument_spec
def __init__(self, k8s_kind=None, *args, **kwargs):