diff --git a/roles/openshift-applier/README.md b/roles/openshift-applier/README.md index 0608f76..3293a3b 100644 --- a/roles/openshift-applier/README.md +++ b/roles/openshift-applier/README.md @@ -51,7 +51,7 @@ openshift_cluster_content: file: action: # Optional: Defaults to 'apply' no_log: # Optional: no_log at content level if functionality desired. Defaults to False - tags: # Optional: Tags are only needed if `include_tags` is used + tags: # Optional: Tags are only needed if `include_tags` or `exclude_tags` is used - tag1 - tag2 post_steps: # Optional: post-steps at content level can be added if desired @@ -212,12 +212,15 @@ metadata: ### Filtering content based on tags -The `openshift-applier` supports the use of tags in the inventory (see example above) to allow for filtering which content should be processed and not. The `include_tags` variable/fact takes a comma separated list of tags that will be processed and only content with matching tags will be applied. +The `openshift-applier` supports the use of tags in the inventory (see example above) to allow for filtering which content should be processed and not. The `include_tags` and `exclude_tags` variables/facts take a comma separated list of tags that will be processed. The `include_tags` will apply content **only** with the matching tags, while `exclude_tags` will apply **anything but** the content with the matching tags. -**_NOTE:_** Entries in the inventory without tags will not be processed when a valid list is supplied with the `include_tags` option. +**_NOTE:_** Entries in the inventory without tags will not be processed when a valid list of tags is supplied with `include_tags`. + +**_NOTE:_** If the same tag exists in both `include_tags` and `exclude_tags` the run will error out. Likewise, if tags from the two options annuls each other, the execution will also error out. ``` include_tags=tag1,tag2 +exclude_tags=tag3,tag4 ``` diff --git a/roles/openshift-applier/defaults/main.yml b/roles/openshift-applier/defaults/main.yml index f887ee4..0e4883f 100644 --- a/roles/openshift-applier/defaults/main.yml +++ b/roles/openshift-applier/defaults/main.yml @@ -3,6 +3,7 @@ client: oc default_oc_action: apply tmp_inv_dir: '' include_tags: '' +exclude_tags: '' provision: True params_from_vars: {} oc_ignore_unknown_parameters: true diff --git a/roles/openshift-applier/filter_plugins/applier-filters.py b/roles/openshift-applier/filter_plugins/applier-filters.py index 6564598..01b04f0 100644 --- a/roles/openshift-applier/filter_plugins/applier-filters.py +++ b/roles/openshift-applier/filter_plugins/applier-filters.py @@ -8,30 +8,83 @@ from urllib.request import urlopen +# Return content invoked with improper tag usage +def get_invalid_tag_usage(applier_list, include_tags, exclude_tags): + + if len(include_tags.strip()) == 0 or len(exclude_tags.strip()) == 0: + return [] + + # Convert comma seperated list to an actual list and strip off whitespaces of each element + include_list = include_tags.split(",") + include_list = [i.strip() for i in include_list] + + exclude_list = exclude_tags.split(",") + exclude_list = [i.strip() for i in exclude_list] + + repeated_tags = [] + # Loop through the main list to check if any tags were invoked incorrectly + # - append to repeated_tags and return + for a in applier_list[:]: + if 'content' in a: + for c in a['content'][:]: + if 'tags' not in c: + continue + + include_matches = list(set(include_list) & set(c['tags'])) + + exclude_matches = list(set(exclude_list) & set(c['tags'])) + + if include_matches and exclude_matches: + repeated_tags.append({ + 'object': a.get("object", "[unknown]"), + 'name': c.get("name", "[unknown]"), + 'invoked_tags': include_matches + exclude_matches, + }) + + return repeated_tags + + # Helper function to simplify the 'filter_applier_items' below -def filter_content(content_dict, outer_list, filter_list): - # If tags don't exists at all, just remove the 'content' +def filter_content(content_dict, outer_list, include_list, exclude_list): + # Handle if tags don't exist in the 'content' section if 'tags' not in content_dict: - outer_list.remove(content_dict) + if include_list: + outer_list.remove(content_dict) return - # Find out of if any of the tags exists in the 'content' section - intersect_list = [val for val in content_dict['tags'] if val in filter_list] + # Handle if include_tags exists in the 'content' section + if include_list: + intersect_list = [ + val for val in content_dict['tags'] if val in include_list] + if len(intersect_list) == 0: + outer_list.remove(content_dict) + return - # If none of the filter tags exists, remove it from the list - if len(intersect_list) == 0: - outer_list.remove(content_dict) + # Handle if exclude_tags exists in the 'content' section + if exclude_list: + intersect_list = [ + val for val in content_dict['tags'] if val in exclude_list] + if len(intersect_list) != 0: + outer_list.remove(content_dict) + return # Main 'filter_applier_items' function -def filter_applier_items(applier_list, include_tags): - # If no filter tags supplied - just return list as-is - if len(include_tags.strip()) == 0: +def filter_applier_items(applier_list, include_tags, exclude_tags): + # If no tag lists supplied - just return list as-is + if len(include_tags.strip()) == 0 and len(exclude_tags.strip()) == 0: return applier_list + exclude_list, include_list = [], [] + # Convert comma seperated list to an actual list and strip off whitespaces of each element - filter_list = include_tags.split(",") - filter_list = [i.strip() for i in filter_list] + if include_tags and len(include_tags.strip()) != 0: + include_list = include_tags.split(",") + include_list = [i.strip() for i in include_list] + + if exclude_tags and len(exclude_tags.strip()) != 0: + exclude_list = exclude_tags.split(",") + exclude_list = [i.strip() for i in exclude_list] # Loop through the main list to check tags # - use a copy to allow for elements to be removed at the same time as we iterrate @@ -39,13 +92,14 @@ def filter_applier_items(applier_list, include_tags): # Handle the 'content' entries if 'content' in a: for c in a['content'][:]: - filter_content(c, a['content'], filter_list) + filter_content(c, a['content'], include_list, exclude_list) if len(a['content']) == 0: applier_list.remove(a) return applier_list + # Function used to determine a files location - i.e.: URL, local file/directory or "something else" def check_file_location(path): # default return values @@ -64,7 +118,7 @@ def check_file_location(path): if (not path_status): try: url_status = urlopen(path) - except (IOError,ValueError): + except (IOError, ValueError): # Must be a pre-loaded template, nothing to do return return_vals @@ -84,8 +138,10 @@ def check_file_location(path): class FilterModule(object): ''' Filters to handle openshift_cluster_content data ''' + def filters(self): return { 'check_file_location': check_file_location, - 'filter_applier_items': filter_applier_items + 'filter_applier_items': filter_applier_items, + 'get_invalid_tag_usage': get_invalid_tag_usage } diff --git a/roles/openshift-applier/tasks/error-on-unsupported.yml b/roles/openshift-applier/tasks/error-on-unsupported.yml index 74c4667..ec09528 100644 --- a/roles/openshift-applier/tasks/error-on-unsupported.yml +++ b/roles/openshift-applier/tasks/error-on-unsupported.yml @@ -24,3 +24,9 @@ when: - unsupported_items is defined with_dict: "{{ unsupported_items }}" + +- name: "Error out if any unsupported tag usage was found" + fail: + msg: "Confused - Do not know how to process the combination of tags for content {{ item.object }}/{{ item.name }} and include_tags/exclude_tags parameters." + with_items: + - "{{ openshift_cluster_content | get_invalid_tag_usage(include_tags, exclude_tags) }}" diff --git a/roles/openshift-applier/tasks/main.yml b/roles/openshift-applier/tasks/main.yml index a1d9940..5f54b24 100644 --- a/roles/openshift-applier/tasks/main.yml +++ b/roles/openshift-applier/tasks/main.yml @@ -26,7 +26,7 @@ - name: "Create OpenShift objects" include_tasks: process-content.yml with_items: - - "{{ openshift_cluster_content | filter_applier_items(include_tags) | default([]) }}" + - "{{ openshift_cluster_content | filter_applier_items(include_tags, exclude_tags) | default([]) }}" loop_control: loop_var: entry when: