mirror of
https://github.com/ansible-collections/kubernetes.core.git
synced 2026-03-26 21:33:02 +00:00
k8s - add label_selectors options (#158)
k8s - add label_selectors options SUMMARY k8s now support label_selectors options same as k8s_info Resolves #43 ISSUE TYPE Feature Pull Request COMPONENT NAME k8s Reviewed-by: Mike Graves <mgraves@redhat.com> Reviewed-by: None <None>
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
---
|
||||
minor_changes:
|
||||
- k8s - add support for label_selectors options (https://github.com/ansible-collections/kubernetes.core/issues/43).
|
||||
@@ -164,6 +164,14 @@
|
||||
tags:
|
||||
- always
|
||||
|
||||
- name: Include label_selectors.yml
|
||||
include_tasks:
|
||||
file: tasks/label_selectors.yml
|
||||
apply:
|
||||
tags: [ label_selectors, k8s ]
|
||||
tags:
|
||||
- always
|
||||
|
||||
- name: Include diff.yml
|
||||
include_tasks:
|
||||
file: tasks/diff.yml
|
||||
|
||||
657
molecule/default/tasks/label_selectors.yml
Normal file
657
molecule/default/tasks/label_selectors.yml
Normal file
@@ -0,0 +1,657 @@
|
||||
---
|
||||
- block:
|
||||
- set_fact:
|
||||
selector_namespace: "selector"
|
||||
selector_pod_delete: "pod-selector-delete"
|
||||
selector_pod_apply: "pod-selector-apply"
|
||||
selector_pod_create:
|
||||
- "pod-selector-apply-00"
|
||||
- "pod-selector-apply-01"
|
||||
- "pod-selector-apply-02"
|
||||
- "pod-selector-apply-03"
|
||||
|
||||
- name: Ensure namespace selector
|
||||
k8s:
|
||||
kind: namespace
|
||||
name: '{{ selector_namespace }}'
|
||||
|
||||
# Resource deletion using label selector (equality-based requirement)
|
||||
- name: Create simple pod
|
||||
k8s:
|
||||
namespace: '{{ selector_namespace }}'
|
||||
definition:
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: '{{ selector_pod_delete }}-00'
|
||||
labels:
|
||||
ansible.dev/team: "cloud"
|
||||
ansible.release/version: upstream
|
||||
ansible.dev/test: "true"
|
||||
spec:
|
||||
containers:
|
||||
- name: c0
|
||||
image: busybox
|
||||
command:
|
||||
- /bin/sh
|
||||
- -c
|
||||
- while true;do date;sleep 5; done
|
||||
|
||||
- name: Delete all resource using selector
|
||||
k8s:
|
||||
state: absent
|
||||
kind: Pod
|
||||
namespace: '{{ selector_namespace }}'
|
||||
label_selectors:
|
||||
- ansible.dev/team=cloud
|
||||
wait: yes
|
||||
wait_timeout: 180
|
||||
|
||||
- name: Ensure resources have been deleted
|
||||
k8s_info:
|
||||
kind: Pod
|
||||
namespace: '{{ selector_namespace }}'
|
||||
label_selectors:
|
||||
- ansible.dev/team=cloud
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- result.resources == []
|
||||
|
||||
# Resource deletion using label selector (set-based requirement)
|
||||
- name: Create simple pod
|
||||
k8s:
|
||||
namespace: '{{ selector_namespace }}'
|
||||
definition:
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: '{{ selector_pod_delete }}-01'
|
||||
labels:
|
||||
environment: production
|
||||
spec:
|
||||
containers:
|
||||
- name: c0
|
||||
image: alpine:3.14.0
|
||||
command:
|
||||
- /bin/sh
|
||||
- -c
|
||||
- while true;do date;sleep 5; done
|
||||
|
||||
- name: Delete all resource using selector
|
||||
k8s:
|
||||
state: absent
|
||||
kind: Pod
|
||||
namespace: '{{ selector_namespace }}'
|
||||
label_selectors:
|
||||
- environment in (test, qa)
|
||||
wait: yes
|
||||
wait_timeout: 180
|
||||
register: result
|
||||
|
||||
- name: check that no resources were deleted
|
||||
assert:
|
||||
that:
|
||||
- result is not changed
|
||||
|
||||
- name: Ensure resources have not been deleted
|
||||
k8s_info:
|
||||
kind: Pod
|
||||
namespace: '{{ selector_namespace }}'
|
||||
label_selectors:
|
||||
- environment in (production)
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- result.resources | list | length > 0
|
||||
|
||||
- name: Delete all resource using selector
|
||||
k8s:
|
||||
state: absent
|
||||
kind: Pod
|
||||
namespace: '{{ selector_namespace }}'
|
||||
label_selectors:
|
||||
- environment in (production)
|
||||
wait: yes
|
||||
wait_timeout: 180
|
||||
register: result
|
||||
|
||||
- name: check result is changed
|
||||
assert:
|
||||
that:
|
||||
- result is changed
|
||||
|
||||
- name: Ensure resources have not been deleted
|
||||
k8s_info:
|
||||
kind: Pod
|
||||
namespace: '{{ selector_namespace }}'
|
||||
label_selectors:
|
||||
- environment in (production)
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- result.resources | list | length == 0
|
||||
|
||||
# Resource creation using label selector
|
||||
- name: Create simple pod using label_selectors option (equality-based requirement)
|
||||
k8s:
|
||||
namespace: '{{ selector_namespace }}'
|
||||
label_selectors:
|
||||
- container.image=fedora
|
||||
definition: |
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: '{{ selector_pod_create[0] }}'
|
||||
labels:
|
||||
container.image: busybox
|
||||
spec:
|
||||
containers:
|
||||
- name: c0
|
||||
image: busybox
|
||||
command:
|
||||
- /bin/sh
|
||||
- -c
|
||||
- while true;do date;sleep 5; done
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: '{{ selector_pod_create[1] }}'
|
||||
labels:
|
||||
container.image: alpine
|
||||
spec:
|
||||
containers:
|
||||
- name: c0
|
||||
image: alpine
|
||||
command:
|
||||
- /bin/sh
|
||||
- -c
|
||||
- while true;do date;sleep 5; done
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: '{{ selector_pod_create[2] }}'
|
||||
labels:
|
||||
container.image: python
|
||||
release: dev
|
||||
spec:
|
||||
containers:
|
||||
- name: c0
|
||||
image: python
|
||||
command:
|
||||
- /bin/sh
|
||||
- -c
|
||||
- while true;do date;sleep 5; done
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: '{{ selector_pod_create[3] }}'
|
||||
labels:
|
||||
container.image: python
|
||||
release: dev
|
||||
spec:
|
||||
containers:
|
||||
- name: c0
|
||||
image: python
|
||||
command:
|
||||
- /bin/sh
|
||||
- -c
|
||||
- while true;do date;sleep 5; done
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- result is not changed
|
||||
|
||||
- name: Create simple pod using label_selectors option
|
||||
k8s:
|
||||
namespace: '{{ selector_namespace }}'
|
||||
label_selectors:
|
||||
- container.image==alpine
|
||||
definition: |
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: '{{ selector_pod_create[0] }}'
|
||||
labels:
|
||||
container.image: busybox
|
||||
spec:
|
||||
containers:
|
||||
- name: c0
|
||||
image: busybox
|
||||
command:
|
||||
- /bin/sh
|
||||
- -c
|
||||
- while true;do date;sleep 5; done
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: '{{ selector_pod_create[1] }}'
|
||||
labels:
|
||||
container.image: alpine
|
||||
spec:
|
||||
containers:
|
||||
- name: c0
|
||||
image: alpine
|
||||
command:
|
||||
- /bin/sh
|
||||
- -c
|
||||
- while true;do date;sleep 5; done
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: '{{ selector_pod_create[2] }}'
|
||||
labels:
|
||||
container.image: python
|
||||
environment: test
|
||||
spec:
|
||||
containers:
|
||||
- name: c0
|
||||
image: python
|
||||
command:
|
||||
- /bin/sh
|
||||
- -c
|
||||
- while true;do date;sleep 5; done
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: '{{ selector_pod_create[3] }}'
|
||||
labels:
|
||||
container.image: python
|
||||
environment: production
|
||||
spec:
|
||||
containers:
|
||||
- name: c0
|
||||
image: python
|
||||
command:
|
||||
- /bin/sh
|
||||
- -c
|
||||
- while true;do date;sleep 5; done
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- result is changed
|
||||
|
||||
- name: list pod created
|
||||
k8s_info:
|
||||
namespace: '{{ selector_namespace }}'
|
||||
kind: Pod
|
||||
label_selectors:
|
||||
- container.image
|
||||
register: pod_created
|
||||
|
||||
- name: Validate that pod with matching label was created
|
||||
assert:
|
||||
that:
|
||||
- pods_created | length == 1
|
||||
- selector_pod_create[1] in pods_created
|
||||
vars:
|
||||
pods_created: '{{ pod_created.resources | map(attribute="metadata.name") | list }}'
|
||||
|
||||
- name: Create simple pod using label_selectors option (set-based requirement)
|
||||
k8s:
|
||||
namespace: '{{ selector_namespace }}'
|
||||
label_selectors:
|
||||
- "!environment"
|
||||
definition: |
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: '{{ selector_pod_create[0] }}'
|
||||
labels:
|
||||
container.image: busybox
|
||||
spec:
|
||||
containers:
|
||||
- name: c0
|
||||
image: busybox
|
||||
command:
|
||||
- /bin/sh
|
||||
- -c
|
||||
- while true;do date;sleep 5; done
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: '{{ selector_pod_create[1] }}'
|
||||
labels:
|
||||
container.image: alpine
|
||||
spec:
|
||||
containers:
|
||||
- name: c0
|
||||
image: alpine
|
||||
command:
|
||||
- /bin/sh
|
||||
- -c
|
||||
- while true;do date;sleep 5; done
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: '{{ selector_pod_create[2] }}'
|
||||
labels:
|
||||
container.image: python
|
||||
environment: test
|
||||
spec:
|
||||
containers:
|
||||
- name: c0
|
||||
image: python
|
||||
command:
|
||||
- /bin/sh
|
||||
- -c
|
||||
- while true;do date;sleep 5; done
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: '{{ selector_pod_create[3] }}'
|
||||
labels:
|
||||
container.image: python
|
||||
environment: production
|
||||
spec:
|
||||
containers:
|
||||
- name: c0
|
||||
image: python
|
||||
command:
|
||||
- /bin/sh
|
||||
- -c
|
||||
- while true;do date;sleep 5; done
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- result is changed
|
||||
|
||||
- name: list pod created
|
||||
k8s_info:
|
||||
namespace: '{{ selector_namespace }}'
|
||||
kind: Pod
|
||||
label_selectors:
|
||||
- container.image
|
||||
register: pod_created
|
||||
|
||||
- name: Validate that pod with matching label was created
|
||||
assert:
|
||||
that:
|
||||
- pods_created | length == 2
|
||||
- selector_pod_create[0] in pods_created
|
||||
vars:
|
||||
pods_created: '{{ pod_created.resources | map(attribute="metadata.name") | list }}'
|
||||
|
||||
- name: Create simple pod using label_selectors option (set-based requirement)
|
||||
k8s:
|
||||
namespace: '{{ selector_namespace }}'
|
||||
label_selectors:
|
||||
- environment in (test)
|
||||
definition: |
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: '{{ selector_pod_create[0] }}'
|
||||
labels:
|
||||
container.image: busybox
|
||||
spec:
|
||||
containers:
|
||||
- name: c0
|
||||
image: busybox
|
||||
command:
|
||||
- /bin/sh
|
||||
- -c
|
||||
- while true;do date;sleep 5; done
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: '{{ selector_pod_create[1] }}'
|
||||
labels:
|
||||
container.image: alpine
|
||||
spec:
|
||||
containers:
|
||||
- name: c0
|
||||
image: alpine
|
||||
command:
|
||||
- /bin/sh
|
||||
- -c
|
||||
- while true;do date;sleep 5; done
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: '{{ selector_pod_create[2] }}'
|
||||
labels:
|
||||
container.image: python
|
||||
environment: test
|
||||
spec:
|
||||
containers:
|
||||
- name: c0
|
||||
image: python
|
||||
command:
|
||||
- /bin/sh
|
||||
- -c
|
||||
- while true;do date;sleep 5; done
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: '{{ selector_pod_create[3] }}'
|
||||
labels:
|
||||
container.image: python
|
||||
environment: production
|
||||
spec:
|
||||
containers:
|
||||
- name: c0
|
||||
image: python
|
||||
command:
|
||||
- /bin/sh
|
||||
- -c
|
||||
- while true;do date;sleep 5; done
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- result is changed
|
||||
|
||||
- name: list pod created
|
||||
k8s_info:
|
||||
namespace: '{{ selector_namespace }}'
|
||||
kind: Pod
|
||||
label_selectors:
|
||||
- container.image
|
||||
register: pod_created
|
||||
|
||||
- name: Validate that pod with matching label was created
|
||||
assert:
|
||||
that:
|
||||
- pods_created | length == 3
|
||||
- selector_pod_create[2] in pods_created
|
||||
vars:
|
||||
pods_created: '{{ pod_created.resources | map(attribute="metadata.name") | list }}'
|
||||
|
||||
- name: Create simple pod using label_selectors option (set-based requirement)
|
||||
k8s:
|
||||
namespace: '{{ selector_namespace }}'
|
||||
label_selectors:
|
||||
- environment notin (test)
|
||||
definition: |
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: '{{ selector_pod_create[0] }}'
|
||||
labels:
|
||||
container.image: busybox
|
||||
spec:
|
||||
containers:
|
||||
- name: c0
|
||||
image: busybox
|
||||
command:
|
||||
- /bin/sh
|
||||
- -c
|
||||
- while true;do date;sleep 5; done
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: '{{ selector_pod_create[1] }}'
|
||||
labels:
|
||||
container.image: alpine
|
||||
spec:
|
||||
containers:
|
||||
- name: c0
|
||||
image: alpine
|
||||
command:
|
||||
- /bin/sh
|
||||
- -c
|
||||
- while true;do date;sleep 5; done
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: '{{ selector_pod_create[2] }}'
|
||||
labels:
|
||||
container.image: python
|
||||
environment: test
|
||||
spec:
|
||||
containers:
|
||||
- name: c0
|
||||
image: python
|
||||
command:
|
||||
- /bin/sh
|
||||
- -c
|
||||
- while true;do date;sleep 5; done
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: '{{ selector_pod_create[3] }}'
|
||||
labels:
|
||||
container.image: python
|
||||
environment: production
|
||||
spec:
|
||||
containers:
|
||||
- name: c0
|
||||
image: python
|
||||
command:
|
||||
- /bin/sh
|
||||
- -c
|
||||
- while true;do date;sleep 5; done
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- result is changed
|
||||
|
||||
- name: list pod created
|
||||
k8s_info:
|
||||
namespace: '{{ selector_namespace }}'
|
||||
kind: Pod
|
||||
label_selectors:
|
||||
- container.image
|
||||
register: pod_created
|
||||
|
||||
- name: Validate that pod with matching label was created
|
||||
assert:
|
||||
that:
|
||||
- pods_created | length == 4
|
||||
- selector_pod_create[3] in pods_created
|
||||
vars:
|
||||
pods_created: '{{ pod_created.resources | map(attribute="metadata.name") | list }}'
|
||||
|
||||
# Resource update using apply
|
||||
- name: Create simple pod using apply
|
||||
k8s:
|
||||
namespace: '{{ selector_namespace }}'
|
||||
apply: yes
|
||||
definition:
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: '{{ selector_pod_apply }}'
|
||||
labels:
|
||||
environment: test
|
||||
spec:
|
||||
containers:
|
||||
- name: c0
|
||||
image: busybox:1.31.0
|
||||
command:
|
||||
- /bin/sh
|
||||
- -c
|
||||
- while true;do date;sleep 5; done
|
||||
|
||||
- name: Apply new pod definition using label_selectors (no match)
|
||||
k8s:
|
||||
namespace: '{{ selector_namespace }}'
|
||||
apply: yes
|
||||
definition:
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: '{{ selector_pod_apply }}'
|
||||
labels:
|
||||
environment: test
|
||||
spec:
|
||||
containers:
|
||||
- name: c0
|
||||
image: busybox:1.33.0
|
||||
command:
|
||||
- /bin/sh
|
||||
- -c
|
||||
- while true;do date;sleep 5; done
|
||||
label_selectors:
|
||||
- environment=qa
|
||||
register: result
|
||||
|
||||
- name: check task output
|
||||
assert:
|
||||
that:
|
||||
- result is not changed
|
||||
- '"filtered by label_selectors" in result.msg'
|
||||
|
||||
- name: Apply new pod definition using label_selectors
|
||||
k8s:
|
||||
namespace: '{{ selector_namespace }}'
|
||||
apply: yes
|
||||
definition:
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: '{{ selector_pod_apply }}'
|
||||
labels:
|
||||
environment: test
|
||||
spec:
|
||||
containers:
|
||||
- name: c0
|
||||
image: busybox:1.33.0
|
||||
command:
|
||||
- /bin/sh
|
||||
- -c
|
||||
- while true;do date;sleep 5; done
|
||||
label_selectors:
|
||||
- environment!=qa
|
||||
register: result
|
||||
|
||||
- name: check task output
|
||||
assert:
|
||||
that:
|
||||
- result is changed
|
||||
|
||||
always:
|
||||
- name: Ensure namespace is deleted
|
||||
k8s:
|
||||
kind: Namespace
|
||||
name: '{{ selector_namespace }}'
|
||||
state: absent
|
||||
ignore_errors: true
|
||||
@@ -29,6 +29,7 @@ from distutils.version import LooseVersion
|
||||
|
||||
from ansible_collections.kubernetes.core.plugins.module_utils.args_common import (AUTH_ARG_MAP, AUTH_ARG_SPEC, AUTH_PROXY_HEADERS_SPEC)
|
||||
from ansible_collections.kubernetes.core.plugins.module_utils.hashes import generate_hash
|
||||
from ansible_collections.kubernetes.core.plugins.module_utils.selector import LabelSelectorFilter
|
||||
|
||||
from ansible.module_utils.basic import missing_required_lib
|
||||
from ansible.module_utils.six import iteritems, string_types
|
||||
@@ -349,7 +350,7 @@ class K8sAnsibleMixin(object):
|
||||
def fail(self, msg=None):
|
||||
self.fail_json(msg=msg)
|
||||
|
||||
def _wait_for(self, resource, name, namespace, predicate, sleep, timeout, state):
|
||||
def _wait_for(self, resource, name, namespace, predicate, sleep, timeout, state, label_selectors):
|
||||
start = datetime.now()
|
||||
|
||||
def _wait_for_elapsed():
|
||||
@@ -358,7 +359,10 @@ class K8sAnsibleMixin(object):
|
||||
response = None
|
||||
while _wait_for_elapsed() < timeout:
|
||||
try:
|
||||
response = resource.get(name=name, namespace=namespace)
|
||||
params = dict(name=name, namespace=namespace)
|
||||
if label_selectors:
|
||||
params['label_selector'] = ','.join(label_selectors)
|
||||
response = resource.get(**params)
|
||||
if predicate(response):
|
||||
if response:
|
||||
return True, response.to_dict(), _wait_for_elapsed()
|
||||
@@ -371,7 +375,7 @@ class K8sAnsibleMixin(object):
|
||||
response = response.to_dict()
|
||||
return False, response, _wait_for_elapsed()
|
||||
|
||||
def wait(self, resource, definition, sleep, timeout, state='present', condition=None):
|
||||
def wait(self, resource, definition, sleep, timeout, state='present', condition=None, label_selectors=None):
|
||||
|
||||
def _deployment_ready(deployment):
|
||||
# FIXME: frustratingly bool(deployment.status) is True even if status is empty
|
||||
@@ -420,7 +424,7 @@ class K8sAnsibleMixin(object):
|
||||
return False
|
||||
|
||||
def _resource_absent(resource):
|
||||
return not resource
|
||||
return not resource or (resource.kind.endswith('List') and resource.items == [])
|
||||
|
||||
waiter = dict(
|
||||
Deployment=_deployment_ready,
|
||||
@@ -428,13 +432,13 @@ class K8sAnsibleMixin(object):
|
||||
Pod=_pod_ready
|
||||
)
|
||||
kind = definition['kind']
|
||||
if state == 'present' and not condition:
|
||||
predicate = waiter.get(kind, lambda x: x)
|
||||
elif state == 'present' and condition:
|
||||
predicate = _custom_condition
|
||||
if state == 'present':
|
||||
predicate = waiter.get(kind, lambda x: x) if not condition else _custom_condition
|
||||
else:
|
||||
predicate = _resource_absent
|
||||
return self._wait_for(resource, definition['metadata']['name'], definition['metadata'].get('namespace'), predicate, sleep, timeout, state)
|
||||
name = definition['metadata']['name']
|
||||
namespace = definition['metadata'].get('namespace')
|
||||
return self._wait_for(resource, name, namespace, predicate, sleep, timeout, state, label_selectors)
|
||||
|
||||
def set_resource_definitions(self, module):
|
||||
resource_definition = module.params.get('resource_definition')
|
||||
@@ -575,6 +579,7 @@ class K8sAnsibleMixin(object):
|
||||
wait_timeout = self.params.get('wait_timeout')
|
||||
wait_condition = None
|
||||
continue_on_error = self.params.get('continue_on_error')
|
||||
label_selectors = self.params.get('label_selectors')
|
||||
if self.params.get('wait_condition') and self.params['wait_condition'].get('type'):
|
||||
wait_condition = self.params['wait_condition']
|
||||
|
||||
@@ -591,6 +596,8 @@ class K8sAnsibleMixin(object):
|
||||
params = dict(name=name)
|
||||
if namespace:
|
||||
params['namespace'] = namespace
|
||||
if label_selectors:
|
||||
params['label_selector'] = ','.join(label_selectors)
|
||||
existing = resource.get(**params)
|
||||
except (NotFoundError, MethodNotAllowedError):
|
||||
# Remove traceback so that it doesn't show up in later failures
|
||||
@@ -625,7 +632,13 @@ class K8sAnsibleMixin(object):
|
||||
|
||||
if state == 'absent':
|
||||
result['method'] = "delete"
|
||||
if not existing:
|
||||
|
||||
def _empty_resource_list():
|
||||
if existing and existing.kind.endswith('List'):
|
||||
return existing.items == []
|
||||
return False
|
||||
|
||||
if not existing or _empty_resource_list():
|
||||
# The object already does not exist
|
||||
return result
|
||||
else:
|
||||
@@ -651,7 +664,7 @@ class K8sAnsibleMixin(object):
|
||||
else:
|
||||
self.fail_json(msg=build_error_msg(definition['kind'], origin_name, msg), error=exc.status, status=exc.status, reason=exc.reason)
|
||||
if wait:
|
||||
success, resource, duration = self.wait(resource, definition, wait_sleep, wait_timeout, 'absent')
|
||||
success, resource, duration = self.wait(resource, definition, wait_sleep, wait_timeout, 'absent', label_selectors=label_selectors)
|
||||
result['duration'] = duration
|
||||
if not success:
|
||||
msg = "Resource deletion timed out"
|
||||
@@ -663,6 +676,13 @@ class K8sAnsibleMixin(object):
|
||||
return result
|
||||
|
||||
else:
|
||||
if label_selectors:
|
||||
filter_selector = LabelSelectorFilter(label_selectors)
|
||||
if not filter_selector.isMatching(definition):
|
||||
result['changed'] = False
|
||||
result['msg'] = "resource 'kind={kind},name={name},namespace={namespace}' filtered by label_selectors.".format(
|
||||
kind=definition['kind'], name=origin_name, namespace=namespace)
|
||||
return result
|
||||
if apply:
|
||||
if self.check_mode:
|
||||
ignored, patch = apply_object(resource, _encode_stringdata(definition))
|
||||
|
||||
71
plugins/module_utils/selector.py
Normal file
71
plugins/module_utils/selector.py
Normal file
@@ -0,0 +1,71 @@
|
||||
# Copyright [2021] [Red Hat, Inc.]
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import re
|
||||
|
||||
|
||||
class Selector(object):
|
||||
|
||||
equality_based_operators = ('==', '!=', '=')
|
||||
|
||||
def __init__(self, data):
|
||||
self._operator = None
|
||||
self._data = None
|
||||
if not self.parse_set_based_requirement(data):
|
||||
no_whitespace_data = data.replace(" ", "")
|
||||
for op in self.equality_based_operators:
|
||||
idx = no_whitespace_data.find(op)
|
||||
if idx != -1:
|
||||
self._operator = "in" if op == '==' or op == '=' else "notin"
|
||||
self._key = no_whitespace_data[0:idx]
|
||||
self._data = [no_whitespace_data[idx + len(op):]]
|
||||
break
|
||||
|
||||
def parse_set_based_requirement(self, data):
|
||||
m = re.match(r'( *)([a-z0-9A-Z][a-z0-9A-Z\._-]*[a-z0-9A-Z])( +)(notin|in)( +)\((.*)\)( *)', data)
|
||||
if m:
|
||||
self._set_based_requirement = True
|
||||
self._key = m.group(2)
|
||||
self._operator = m.group(4)
|
||||
self._data = [x.replace(' ', '') for x in m.group(6).split(',') if x != '']
|
||||
return True
|
||||
elif all([x not in data for x in self.equality_based_operators]):
|
||||
self._key = data.rstrip(" ").lstrip(" ")
|
||||
if self._key.startswith("!"):
|
||||
self._key = self._key[1:].lstrip(" ")
|
||||
self._operator = "!"
|
||||
return True
|
||||
return False
|
||||
|
||||
def isMatch(self, labels):
|
||||
if self._operator == "in":
|
||||
return self._key in labels and labels.get(self._key) in self._data
|
||||
elif self._operator == "notin":
|
||||
return self._key not in labels or labels.get(self._key) not in self._data
|
||||
else:
|
||||
return self._key not in labels if self._operator == "!" else self._key in labels
|
||||
|
||||
|
||||
class LabelSelectorFilter(object):
|
||||
|
||||
def __init__(self, label_selectors):
|
||||
self.selectors = [Selector(data) for data in label_selectors]
|
||||
|
||||
def isMatching(self, definition):
|
||||
if "metadata" not in definition or "labels" not in definition['metadata']:
|
||||
return False
|
||||
labels = definition['metadata']['labels']
|
||||
if not isinstance(labels, dict):
|
||||
return None
|
||||
return all([sel.isMatch(labels) for sel in self.selectors])
|
||||
@@ -135,6 +135,12 @@ options:
|
||||
type: bool
|
||||
default: False
|
||||
version_added: 2.0.0
|
||||
label_selectors:
|
||||
description:
|
||||
- Selector (label query) to filter on.
|
||||
type: list
|
||||
elements: str
|
||||
version_added: 2.2.0
|
||||
|
||||
requirements:
|
||||
- "python >= 3.6"
|
||||
@@ -335,6 +341,7 @@ def argspec():
|
||||
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)
|
||||
argument_spec['label_selectors'] = dict(type='list', elements='str')
|
||||
|
||||
return argument_spec
|
||||
|
||||
|
||||
@@ -240,4 +240,8 @@ plugins/modules/k8s_cp.py import-2.6!skip
|
||||
plugins/modules/k8s_cp.py import-2.7!skip
|
||||
molecule/default/roles/k8scopy/library/kubectl_file_compare.py compile-2.6!skip
|
||||
molecule/default/roles/k8scopy/library/kubectl_file_compare.py compile-2.7!skip
|
||||
molecule/default/roles/k8scopy/library/kubectl_file_compare.py compile-3.5!skip
|
||||
molecule/default/roles/k8scopy/library/kubectl_file_compare.py compile-3.5!skip
|
||||
tests/unit/module_utils/test_selector.py future-import-boilerplate!skip
|
||||
tests/unit/module_utils/test_selector.py metaclass-boilerplate!skip
|
||||
plugins/module_utils/selector.py future-import-boilerplate!skip
|
||||
plugins/module_utils/selector.py metaclass-boilerplate!skip
|
||||
@@ -240,4 +240,8 @@ plugins/modules/k8s_cp.py import-2.6!skip
|
||||
plugins/modules/k8s_cp.py import-2.7!skip
|
||||
molecule/default/roles/k8scopy/library/kubectl_file_compare.py compile-2.6!skip
|
||||
molecule/default/roles/k8scopy/library/kubectl_file_compare.py compile-2.7!skip
|
||||
molecule/default/roles/k8scopy/library/kubectl_file_compare.py compile-3.5!skip
|
||||
molecule/default/roles/k8scopy/library/kubectl_file_compare.py compile-3.5!skip
|
||||
tests/unit/module_utils/test_selector.py future-import-boilerplate!skip
|
||||
tests/unit/module_utils/test_selector.py metaclass-boilerplate!skip
|
||||
plugins/module_utils/selector.py future-import-boilerplate!skip
|
||||
plugins/module_utils/selector.py metaclass-boilerplate!skip
|
||||
@@ -238,4 +238,6 @@ tests/sanity/refresh_ignore_files shebang!skip
|
||||
plugins/modules/k8s_cp.py compile-2.6!skip
|
||||
plugins/modules/k8s_cp.py compile-2.7!skip
|
||||
plugins/modules/k8s_cp.py import-2.6!skip
|
||||
plugins/modules/k8s_cp.py import-2.7!skip
|
||||
plugins/modules/k8s_cp.py import-2.7!skip
|
||||
plugins/module_utils/selector.py future-import-boilerplate!skip
|
||||
plugins/module_utils/selector.py metaclass-boilerplate!skip
|
||||
@@ -234,4 +234,8 @@ plugins/modules/k8s_cp.py import-2.6!skip
|
||||
plugins/modules/k8s_cp.py import-2.7!skip
|
||||
molecule/default/roles/k8scopy/library/kubectl_file_compare.py compile-2.6!skip
|
||||
molecule/default/roles/k8scopy/library/kubectl_file_compare.py compile-2.7!skip
|
||||
molecule/default/roles/k8scopy/library/kubectl_file_compare.py compile-3.5!skip
|
||||
molecule/default/roles/k8scopy/library/kubectl_file_compare.py compile-3.5!skip
|
||||
tests/unit/module_utils/test_selector.py future-import-boilerplate!skip
|
||||
tests/unit/module_utils/test_selector.py metaclass-boilerplate!skip
|
||||
plugins/module_utils/selector.py future-import-boilerplate!skip
|
||||
plugins/module_utils/selector.py metaclass-boilerplate!skip
|
||||
187
tests/unit/module_utils/test_selector.py
Normal file
187
tests/unit/module_utils/test_selector.py
Normal file
@@ -0,0 +1,187 @@
|
||||
# Copyright [2021] [Red Hat, Inc.]
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from ansible_collections.kubernetes.core.plugins.module_utils.selector import LabelSelectorFilter, Selector
|
||||
|
||||
prod_definition = {
|
||||
'apiVersion': 'v1',
|
||||
'kind': 'Pod',
|
||||
'metadata': {
|
||||
'name': 'test',
|
||||
'labels': {
|
||||
'environment': 'production',
|
||||
'app': 'nginx',
|
||||
}
|
||||
},
|
||||
'spec': {
|
||||
'containers': [
|
||||
{'name': 'nginx', 'image': 'nginx:1.14.2', 'command': ['/bin/sh', '-c', 'sleep 10']}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
no_label_definition = {
|
||||
'apiVersion': 'v1',
|
||||
'kind': 'Pod',
|
||||
'metadata': {
|
||||
'name': 'test',
|
||||
'labels': {}
|
||||
},
|
||||
'spec': {
|
||||
'containers': [
|
||||
{'name': 'nginx', 'image': 'nginx:1.14.2', 'command': ['/bin/sh', '-c', 'sleep 10']}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
test_definition = {
|
||||
'apiVersion': 'v1',
|
||||
'kind': 'Pod',
|
||||
'metadata': {
|
||||
'name': 'test',
|
||||
'labels': {
|
||||
'environment': 'test',
|
||||
'app': 'nginx',
|
||||
}
|
||||
},
|
||||
'spec': {
|
||||
'containers': [
|
||||
{'name': 'nginx', 'image': 'nginx:1.15.2', 'command': ['/bin/sh', '-c', 'sleep 10']}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def test_selector_parser():
|
||||
f_selector = "environment==true"
|
||||
sel = Selector(f_selector)
|
||||
assert sel._operator == "in" and sel._data == ["true"] and sel._key == "environment"
|
||||
f_selector = "environment=true"
|
||||
sel = Selector(f_selector)
|
||||
assert sel._operator == "in" and sel._data == ["true"] and sel._key == "environment"
|
||||
f_selector = " environment == true "
|
||||
sel = Selector(f_selector)
|
||||
assert sel._operator == "in" and sel._data == ["true"] and sel._key == "environment"
|
||||
f_selector = "environment!=false"
|
||||
sel = Selector(f_selector)
|
||||
assert sel._operator == "notin" and sel._data == ["false"] and sel._key == "environment"
|
||||
f_selector = "environment notin (true, false)"
|
||||
sel = Selector(f_selector)
|
||||
assert sel._operator == "notin" and "true" in sel._data and "false" in sel._data and sel._key == "environment"
|
||||
f_selector = "environment in (true, false)"
|
||||
sel = Selector(f_selector)
|
||||
assert sel._operator == "in" and "true" in sel._data and "false" in sel._data and sel._key == "environment"
|
||||
f_selector = "environmentin(true, false)"
|
||||
sel = Selector(f_selector)
|
||||
assert not sel._operator and not sel._data and sel._key == f_selector
|
||||
f_selector = "environment notin (true, false"
|
||||
sel = Selector(f_selector)
|
||||
assert not sel._operator and not sel._data and sel._key == f_selector
|
||||
f_selector = "!environment"
|
||||
sel = Selector(f_selector)
|
||||
assert sel._operator == "!" and not sel._data and sel._key == "environment"
|
||||
f_selector = "! environment "
|
||||
sel = Selector(f_selector)
|
||||
assert sel._operator == "!" and not sel._data and sel._key == "environment"
|
||||
|
||||
|
||||
def test_label_selector_without_operator():
|
||||
label_selector = ['environment', 'app']
|
||||
assert LabelSelectorFilter(label_selector).isMatching(prod_definition)
|
||||
assert not LabelSelectorFilter(label_selector).isMatching(no_label_definition)
|
||||
assert LabelSelectorFilter(label_selector).isMatching(test_definition)
|
||||
|
||||
|
||||
def test_label_selector_equal_operator():
|
||||
label_selector = ['environment==test']
|
||||
assert not LabelSelectorFilter(label_selector).isMatching(prod_definition)
|
||||
assert not LabelSelectorFilter(label_selector).isMatching(no_label_definition)
|
||||
assert LabelSelectorFilter(label_selector).isMatching(test_definition)
|
||||
label_selector = ['environment=production']
|
||||
assert LabelSelectorFilter(label_selector).isMatching(prod_definition)
|
||||
assert not LabelSelectorFilter(label_selector).isMatching(no_label_definition)
|
||||
assert not LabelSelectorFilter(label_selector).isMatching(test_definition)
|
||||
label_selector = ['environment=production', 'app==mongodb']
|
||||
assert not LabelSelectorFilter(label_selector).isMatching(prod_definition)
|
||||
assert not LabelSelectorFilter(label_selector).isMatching(no_label_definition)
|
||||
assert not LabelSelectorFilter(label_selector).isMatching(test_definition)
|
||||
label_selector = ['environment=production', 'app==nginx']
|
||||
assert LabelSelectorFilter(label_selector).isMatching(prod_definition)
|
||||
assert not LabelSelectorFilter(label_selector).isMatching(no_label_definition)
|
||||
assert not LabelSelectorFilter(label_selector).isMatching(test_definition)
|
||||
label_selector = ['environment', 'app==nginx']
|
||||
assert LabelSelectorFilter(label_selector).isMatching(prod_definition)
|
||||
assert not LabelSelectorFilter(label_selector).isMatching(no_label_definition)
|
||||
assert LabelSelectorFilter(label_selector).isMatching(test_definition)
|
||||
|
||||
|
||||
def test_label_selector_notequal_operator():
|
||||
label_selector = ['environment!=test']
|
||||
assert LabelSelectorFilter(label_selector).isMatching(prod_definition)
|
||||
assert LabelSelectorFilter(label_selector).isMatching(no_label_definition)
|
||||
assert not LabelSelectorFilter(label_selector).isMatching(test_definition)
|
||||
label_selector = ['environment!=production']
|
||||
assert not LabelSelectorFilter(label_selector).isMatching(prod_definition)
|
||||
assert LabelSelectorFilter(label_selector).isMatching(no_label_definition)
|
||||
assert LabelSelectorFilter(label_selector).isMatching(test_definition)
|
||||
label_selector = ['environment=production', 'app!=mongodb']
|
||||
assert LabelSelectorFilter(label_selector).isMatching(prod_definition)
|
||||
assert not LabelSelectorFilter(label_selector).isMatching(no_label_definition)
|
||||
assert not LabelSelectorFilter(label_selector).isMatching(test_definition)
|
||||
label_selector = ['environment=production', 'app!=nginx']
|
||||
assert not LabelSelectorFilter(label_selector).isMatching(prod_definition)
|
||||
assert not LabelSelectorFilter(label_selector).isMatching(no_label_definition)
|
||||
assert not LabelSelectorFilter(label_selector).isMatching(test_definition)
|
||||
label_selector = ['environment', 'app!=nginx']
|
||||
assert not LabelSelectorFilter(label_selector).isMatching(prod_definition)
|
||||
assert not LabelSelectorFilter(label_selector).isMatching(no_label_definition)
|
||||
assert not LabelSelectorFilter(label_selector).isMatching(test_definition)
|
||||
|
||||
|
||||
def test_label_selector_conflicting_definition():
|
||||
label_selector = ['environment==test', 'environment!=test']
|
||||
assert not LabelSelectorFilter(label_selector).isMatching(prod_definition)
|
||||
assert not LabelSelectorFilter(label_selector).isMatching(no_label_definition)
|
||||
assert not LabelSelectorFilter(label_selector).isMatching(test_definition)
|
||||
label_selector = ['environment==test', 'environment==production']
|
||||
assert not LabelSelectorFilter(label_selector).isMatching(prod_definition)
|
||||
assert not LabelSelectorFilter(label_selector).isMatching(no_label_definition)
|
||||
assert not LabelSelectorFilter(label_selector).isMatching(test_definition)
|
||||
|
||||
|
||||
def test_set_based_requirement():
|
||||
label_selector = ['environment in (production)']
|
||||
assert LabelSelectorFilter(label_selector).isMatching(prod_definition)
|
||||
assert not LabelSelectorFilter(label_selector).isMatching(no_label_definition)
|
||||
assert not LabelSelectorFilter(label_selector).isMatching(test_definition)
|
||||
label_selector = ['environment in (production, test)']
|
||||
assert LabelSelectorFilter(label_selector).isMatching(prod_definition)
|
||||
assert not LabelSelectorFilter(label_selector).isMatching(no_label_definition)
|
||||
assert LabelSelectorFilter(label_selector).isMatching(test_definition)
|
||||
label_selector = ['environment notin (production)']
|
||||
assert not LabelSelectorFilter(label_selector).isMatching(prod_definition)
|
||||
assert LabelSelectorFilter(label_selector).isMatching(no_label_definition)
|
||||
assert LabelSelectorFilter(label_selector).isMatching(test_definition)
|
||||
label_selector = ['environment notin (production, test)']
|
||||
assert not LabelSelectorFilter(label_selector).isMatching(prod_definition)
|
||||
assert LabelSelectorFilter(label_selector).isMatching(no_label_definition)
|
||||
assert not LabelSelectorFilter(label_selector).isMatching(test_definition)
|
||||
label_selector = ['environment']
|
||||
assert LabelSelectorFilter(label_selector).isMatching(prod_definition)
|
||||
assert not LabelSelectorFilter(label_selector).isMatching(no_label_definition)
|
||||
assert LabelSelectorFilter(label_selector).isMatching(test_definition)
|
||||
label_selector = ['!environment']
|
||||
assert not LabelSelectorFilter(label_selector).isMatching(prod_definition)
|
||||
assert LabelSelectorFilter(label_selector).isMatching(no_label_definition)
|
||||
assert not LabelSelectorFilter(label_selector).isMatching(test_definition)
|
||||
Reference in New Issue
Block a user