mirror of
https://github.com/ansible-collections/community.general.git
synced 2024-09-14 20:13:21 +02:00
Merge pull request #10183 from bcoca/tag_control
adds complex tag management
This commit is contained in:
commit
c81d981164
7 changed files with 138 additions and 67 deletions
|
@ -227,22 +227,6 @@ def main(args):
|
||||||
label = play.name
|
label = play.name
|
||||||
hosts = pb.inventory.list_hosts(play.hosts)
|
hosts = pb.inventory.list_hosts(play.hosts)
|
||||||
|
|
||||||
# Filter all tasks by given tags
|
|
||||||
if pb.only_tags != 'all':
|
|
||||||
if options.subset and not hosts:
|
|
||||||
continue
|
|
||||||
matched_tags, unmatched_tags = play.compare_tags(pb.only_tags)
|
|
||||||
|
|
||||||
# Remove skipped tasks
|
|
||||||
matched_tags = matched_tags - set(pb.skip_tags)
|
|
||||||
|
|
||||||
unmatched_tags.discard('all')
|
|
||||||
unknown_tags = ((set(pb.only_tags) | set(pb.skip_tags)) -
|
|
||||||
(matched_tags | unmatched_tags))
|
|
||||||
|
|
||||||
if unknown_tags:
|
|
||||||
continue
|
|
||||||
|
|
||||||
if options.listhosts:
|
if options.listhosts:
|
||||||
print ' play #%d (%s): host count=%d' % (playnum, label, len(hosts))
|
print ' play #%d (%s): host count=%d' % (playnum, label, len(hosts))
|
||||||
for host in hosts:
|
for host in hosts:
|
||||||
|
@ -251,9 +235,7 @@ def main(args):
|
||||||
if options.listtasks:
|
if options.listtasks:
|
||||||
print ' play #%d (%s):' % (playnum, label)
|
print ' play #%d (%s):' % (playnum, label)
|
||||||
|
|
||||||
for task in play.tasks():
|
for task in pb.tasks_to_run_in_play(play):
|
||||||
if (set(task.tags).intersection(pb.only_tags) and not
|
|
||||||
set(task.tags).intersection(pb.skip_tags)):
|
|
||||||
if getattr(task, 'name', None) is not None:
|
if getattr(task, 'name', None) is not None:
|
||||||
# meta tasks have no names
|
# meta tasks have no names
|
||||||
print ' %s' % task.name
|
print ' %s' % task.name
|
||||||
|
|
|
@ -40,6 +40,28 @@ And you may also tag basic include statements::
|
||||||
|
|
||||||
Both of these have the function of tagging every single task inside the include statement.
|
Both of these have the function of tagging every single task inside the include statement.
|
||||||
|
|
||||||
|
|
||||||
|
Special Tags
|
||||||
|
````````````
|
||||||
|
|
||||||
|
There is a special 'always' tag that will always run a task, unless specifically skipped (--skip-tags always)
|
||||||
|
|
||||||
|
Example::
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
|
||||||
|
- debug: msg="Always runs"
|
||||||
|
tags:
|
||||||
|
- always
|
||||||
|
|
||||||
|
- debug: msg="runs when you use tag1"
|
||||||
|
tags:
|
||||||
|
- tag1
|
||||||
|
|
||||||
|
There are another 3 special keywords for tags, 'tagged', 'untagged' and 'all', which run only tagged, only untagged
|
||||||
|
and all tasks respectively. By default ansible runs as if --tags all had been specified.
|
||||||
|
|
||||||
|
|
||||||
.. seealso::
|
.. seealso::
|
||||||
|
|
||||||
:doc:`playbooks`
|
:doc:`playbooks`
|
||||||
|
|
|
@ -36,6 +36,7 @@ import pipes
|
||||||
# holds all other variables about a host
|
# holds all other variables about a host
|
||||||
SETUP_CACHE = ansible.cache.FactCache()
|
SETUP_CACHE = ansible.cache.FactCache()
|
||||||
VARS_CACHE = collections.defaultdict(dict)
|
VARS_CACHE = collections.defaultdict(dict)
|
||||||
|
RESERVED_TAGS = ['all','tagged','untagged','always']
|
||||||
|
|
||||||
|
|
||||||
class PlayBook(object):
|
class PlayBook(object):
|
||||||
|
@ -314,6 +315,7 @@ class PlayBook(object):
|
||||||
assert play is not None
|
assert play is not None
|
||||||
|
|
||||||
matched_tags, unmatched_tags = play.compare_tags(self.only_tags)
|
matched_tags, unmatched_tags = play.compare_tags(self.only_tags)
|
||||||
|
|
||||||
matched_tags_all = matched_tags_all | matched_tags
|
matched_tags_all = matched_tags_all | matched_tags
|
||||||
unmatched_tags_all = unmatched_tags_all | unmatched_tags
|
unmatched_tags_all = unmatched_tags_all | unmatched_tags
|
||||||
|
|
||||||
|
@ -332,10 +334,13 @@ class PlayBook(object):
|
||||||
# the user can correct the arguments.
|
# the user can correct the arguments.
|
||||||
unknown_tags = ((set(self.only_tags) | set(self.skip_tags)) -
|
unknown_tags = ((set(self.only_tags) | set(self.skip_tags)) -
|
||||||
(matched_tags_all | unmatched_tags_all))
|
(matched_tags_all | unmatched_tags_all))
|
||||||
unknown_tags.discard('all')
|
|
||||||
|
for t in RESERVED_TAGS:
|
||||||
|
unknown_tags.discard(t)
|
||||||
|
|
||||||
if len(unknown_tags) > 0:
|
if len(unknown_tags) > 0:
|
||||||
unmatched_tags_all.discard('all')
|
for t in RESERVED_TAGS:
|
||||||
|
unmatched_tags_all.discard(t)
|
||||||
msg = 'tag(s) not found in playbook: %s. possible values: %s'
|
msg = 'tag(s) not found in playbook: %s. possible values: %s'
|
||||||
unknown = ','.join(sorted(unknown_tags))
|
unknown = ','.join(sorted(unknown_tags))
|
||||||
unmatched = ','.join(sorted(unmatched_tags_all))
|
unmatched = ','.join(sorted(unmatched_tags_all))
|
||||||
|
@ -667,7 +672,53 @@ class PlayBook(object):
|
||||||
return filename
|
return filename
|
||||||
|
|
||||||
# *****************************************************
|
# *****************************************************
|
||||||
|
def tasks_to_run_in_play(self, play):
|
||||||
|
|
||||||
|
tasks = []
|
||||||
|
|
||||||
|
for task in play.tasks():
|
||||||
|
# only run the task if the requested tags match or has 'always' tag
|
||||||
|
u = set(['untagged'])
|
||||||
|
task_set = set(task.tags)
|
||||||
|
|
||||||
|
if 'always' in task.tags:
|
||||||
|
should_run = True
|
||||||
|
else:
|
||||||
|
if 'all' in self.only_tags:
|
||||||
|
should_run = True
|
||||||
|
else:
|
||||||
|
should_run = False
|
||||||
|
if 'tagged' in self.only_tags:
|
||||||
|
if task_set != u:
|
||||||
|
should_run = True
|
||||||
|
elif 'untagged' in self.only_tags:
|
||||||
|
if task_set == u:
|
||||||
|
should_run = True
|
||||||
|
else:
|
||||||
|
if task_set.intersection(self.only_tags):
|
||||||
|
should_run = True
|
||||||
|
|
||||||
|
# Check for tags that we need to skip
|
||||||
|
if 'all' in self.skip_tags:
|
||||||
|
should_run = False
|
||||||
|
else:
|
||||||
|
if 'tagged' in self.skip_tags:
|
||||||
|
if task_set != u:
|
||||||
|
should_run = False
|
||||||
|
elif 'untagged' in self.skip_tags:
|
||||||
|
if task_set == u:
|
||||||
|
should_run = False
|
||||||
|
else:
|
||||||
|
if should_run:
|
||||||
|
if task_set.intersection(self.skip_tags):
|
||||||
|
should_run = False
|
||||||
|
|
||||||
|
if should_run:
|
||||||
|
tasks.append(task)
|
||||||
|
|
||||||
|
return tasks
|
||||||
|
|
||||||
|
# *****************************************************
|
||||||
def _run_play(self, play):
|
def _run_play(self, play):
|
||||||
''' run a list of tasks for a given pattern, in order '''
|
''' run a list of tasks for a given pattern, in order '''
|
||||||
|
|
||||||
|
@ -720,7 +771,7 @@ class PlayBook(object):
|
||||||
play._play_hosts = self._trim_unavailable_hosts(on_hosts)
|
play._play_hosts = self._trim_unavailable_hosts(on_hosts)
|
||||||
self.inventory.also_restrict_to(on_hosts)
|
self.inventory.also_restrict_to(on_hosts)
|
||||||
|
|
||||||
for task in play.tasks():
|
for task in self.tasks_to_run_in_play(play):
|
||||||
|
|
||||||
if task.meta is not None:
|
if task.meta is not None:
|
||||||
# meta tasks can force handlers to run mid-play
|
# meta tasks can force handlers to run mid-play
|
||||||
|
@ -730,22 +781,6 @@ class PlayBook(object):
|
||||||
# skip calling the handler till the play is finished
|
# skip calling the handler till the play is finished
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# only run the task if the requested tags match
|
|
||||||
should_run = False
|
|
||||||
for x in self.only_tags:
|
|
||||||
|
|
||||||
for y in task.tags:
|
|
||||||
if x == y:
|
|
||||||
should_run = True
|
|
||||||
break
|
|
||||||
|
|
||||||
# Check for tags that we need to skip
|
|
||||||
if should_run:
|
|
||||||
if any(x in task.tags for x in self.skip_tags):
|
|
||||||
should_run = False
|
|
||||||
|
|
||||||
if should_run:
|
|
||||||
|
|
||||||
if not self._run_task(play, task, False):
|
if not self._run_task(play, task, False):
|
||||||
# whether no hosts matched is fatal or not depends if it was on the initial step.
|
# whether no hosts matched is fatal or not depends if it was on the initial step.
|
||||||
# if we got exactly no hosts on the first step (setup!) then the host group
|
# if we got exactly no hosts on the first step (setup!) then the host group
|
||||||
|
|
|
@ -669,20 +669,6 @@ class Play(object):
|
||||||
|
|
||||||
# *************************************************
|
# *************************************************
|
||||||
|
|
||||||
def _is_valid_tag(self, tag_list):
|
|
||||||
"""
|
|
||||||
Check to see if the list of tags passed in is in the list of tags
|
|
||||||
we only want (playbook.only_tags), or if it is not in the list of
|
|
||||||
tags we don't want (playbook.skip_tags).
|
|
||||||
"""
|
|
||||||
matched_skip_tags = set(tag_list) & set(self.playbook.skip_tags)
|
|
||||||
matched_only_tags = set(tag_list) & set(self.playbook.only_tags)
|
|
||||||
if len(matched_skip_tags) > 0 or (self.playbook.only_tags != ['all'] and len(matched_only_tags) == 0):
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
# *************************************************
|
|
||||||
|
|
||||||
def tasks(self):
|
def tasks(self):
|
||||||
''' return task objects for this play '''
|
''' return task objects for this play '''
|
||||||
return self._tasks
|
return self._tasks
|
||||||
|
@ -783,8 +769,27 @@ class Play(object):
|
||||||
# compare the lists of tags using sets and return the matched and unmatched
|
# compare the lists of tags using sets and return the matched and unmatched
|
||||||
all_tags_set = set(all_tags)
|
all_tags_set = set(all_tags)
|
||||||
tags_set = set(tags)
|
tags_set = set(tags)
|
||||||
matched_tags = all_tags_set & tags_set
|
|
||||||
unmatched_tags = all_tags_set - tags_set
|
matched_tags = all_tags_set.intersection(tags_set)
|
||||||
|
unmatched_tags = all_tags_set.difference(tags_set)
|
||||||
|
|
||||||
|
a = set(['always'])
|
||||||
|
u = set(['untagged'])
|
||||||
|
if 'always' in all_tags_set:
|
||||||
|
matched_tags = matched_tags.union(a)
|
||||||
|
unmatched_tags = all_tags_set.difference(a)
|
||||||
|
|
||||||
|
if 'all' in tags_set:
|
||||||
|
matched_tags = matched_tags.union(all_tags_set)
|
||||||
|
unmatched_tags = set()
|
||||||
|
|
||||||
|
if 'tagged' in tags_set:
|
||||||
|
matched_tags = all_tags_set.difference(u)
|
||||||
|
unmatched_tags = u
|
||||||
|
|
||||||
|
if 'untagged' in tags_set and 'untagged' in all_tags_set:
|
||||||
|
matched_tags = matched_tags.union(u)
|
||||||
|
unmatched_tags = unmatched_tags.difference(u)
|
||||||
|
|
||||||
return matched_tags, unmatched_tags
|
return matched_tags, unmatched_tags
|
||||||
|
|
||||||
|
|
|
@ -129,7 +129,7 @@ class Task(object):
|
||||||
|
|
||||||
# load various attributes
|
# load various attributes
|
||||||
self.name = ds.get('name', None)
|
self.name = ds.get('name', None)
|
||||||
self.tags = [ 'all' ]
|
self.tags = [ 'untagged' ]
|
||||||
self.register = ds.get('register', None)
|
self.register = ds.get('register', None)
|
||||||
self.sudo = utils.boolean(ds.get('sudo', play.sudo))
|
self.sudo = utils.boolean(ds.get('sudo', play.sudo))
|
||||||
self.su = utils.boolean(ds.get('su', play.su))
|
self.su = utils.boolean(ds.get('su', play.su))
|
||||||
|
@ -316,6 +316,9 @@ class Task(object):
|
||||||
self.tags.extend(apply_tags)
|
self.tags.extend(apply_tags)
|
||||||
self.tags.extend(import_tags)
|
self.tags.extend(import_tags)
|
||||||
|
|
||||||
|
if len(self.tags) > 1:
|
||||||
|
self.tags.remove('untagged')
|
||||||
|
|
||||||
if additional_conditions:
|
if additional_conditions:
|
||||||
new_conditions = additional_conditions[:]
|
new_conditions = additional_conditions[:]
|
||||||
if self.when:
|
if self.when:
|
||||||
|
|
|
@ -21,7 +21,7 @@ VAULT_PASSWORD_FILE = vault-password
|
||||||
|
|
||||||
CONSUL_RUNNING := $(shell python consul_running.py)
|
CONSUL_RUNNING := $(shell python consul_running.py)
|
||||||
|
|
||||||
all: parsing test_var_precedence unicode non_destructive destructive includes check_mode test_hash test_handlers test_group_by test_vault
|
all: parsing test_var_precedence unicode non_destructive destructive includes check_mode test_hash test_handlers test_group_by test_vault test_tags
|
||||||
|
|
||||||
parsing:
|
parsing:
|
||||||
ansible-playbook bad_parsing.yml -i $(INVENTORY) -e @$(VARS_FILE) $(CREDENTIALS_ARG) -vvv $(TEST_FLAGS) --tags prepare,common,scenario1; [ $$? -eq 3 ]
|
ansible-playbook bad_parsing.yml -i $(INVENTORY) -e @$(VARS_FILE) $(CREDENTIALS_ARG) -vvv $(TEST_FLAGS) --tags prepare,common,scenario1; [ $$? -eq 3 ]
|
||||||
|
@ -82,6 +82,15 @@ test_delegate_to:
|
||||||
test_winrm:
|
test_winrm:
|
||||||
ansible-playbook test_winrm.yml -i inventory.winrm -e @$(VARS_FILE) $(CREDENTIALS_ARG) -v $(TEST_FLAGS)
|
ansible-playbook test_winrm.yml -i inventory.winrm -e @$(VARS_FILE) $(CREDENTIALS_ARG) -v $(TEST_FLAGS)
|
||||||
|
|
||||||
|
test_tags:
|
||||||
|
# Run everything by default
|
||||||
|
[ "$$(ansible-playbook --list-tasks test_tags.yml -i $(INVENTORY) -e @$(VARS_FILE) $(CREDENTIALS_ARG) -v $(TEST_FLAGS) | fgrep Task_with | xargs)" = "Task_with_tag Task_with_always_tag Task_without_tag" ]
|
||||||
|
# Run the exact tags, and always
|
||||||
|
[ "$$(ansible-playbook --list-tasks --tags tag test_tags.yml -i $(INVENTORY) -e @$(VARS_FILE) $(CREDENTIALS_ARG) -v $(TEST_FLAGS) | fgrep Task_with | xargs)" = "Task_with_tag Task_with_always_tag" ]
|
||||||
|
# Skip one tag
|
||||||
|
[ "$$(ansible-playbook --list-tasks --skip-tags tag test_tags.yml -i $(INVENTORY) -e @$(VARS_FILE) $(CREDENTIALS_ARG) -v $(TEST_FLAGS) | fgrep Task_with | xargs)" = "Task_with_always_tag Task_without_tag" ]
|
||||||
|
|
||||||
|
|
||||||
cloud: amazon rackspace
|
cloud: amazon rackspace
|
||||||
|
|
||||||
cloud_cleanup: amazon_cleanup rackspace_cleanup
|
cloud_cleanup: amazon_cleanup rackspace_cleanup
|
||||||
|
|
15
test/integration/test_tags.yml
Normal file
15
test/integration/test_tags.yml
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
---
|
||||||
|
- name: verify tags work as expected
|
||||||
|
hosts: localhost
|
||||||
|
gather_facts: False
|
||||||
|
connection: local
|
||||||
|
tasks:
|
||||||
|
- name: Task_with_tag
|
||||||
|
debug: msg=
|
||||||
|
tags: tag
|
||||||
|
- name: Task_with_always_tag
|
||||||
|
debug: msg=
|
||||||
|
tags: always
|
||||||
|
- name: Task_without_tag
|
||||||
|
debug: msg=
|
||||||
|
|
Loading…
Add table
Reference in a new issue