From 04349ec3626fcf43a60fe6faeae215256bf25f5f Mon Sep 17 00:00:00 2001 From: Alan Descoins Date: Tue, 25 Jun 2013 02:33:29 -0300 Subject: [PATCH] Added --skip-tags option to ansible-playbook. --- bin/ansible-playbook | 37 +++++++++++++++++++++----------- lib/ansible/playbook/__init__.py | 37 ++++++++++++++++++++++---------- 2 files changed, 51 insertions(+), 23 deletions(-) diff --git a/bin/ansible-playbook b/bin/ansible-playbook index 0659a0e8a6..2ee527656e 100755 --- a/bin/ansible-playbook +++ b/bin/ansible-playbook @@ -23,7 +23,7 @@ import os import ansible.playbook import ansible.constants as C -import ansible.utils.template +import ansible.utils.template from ansible import errors from ansible import callbacks from ansible import utils @@ -53,11 +53,11 @@ def main(args): # create parser for CLI options parser = utils.base_parser( - constants=C, + constants=C, usage = "%prog playbook.yml", - connect_opts=True, - runas_opts=True, - subset_opts=True, + connect_opts=True, + runas_opts=True, + subset_opts=True, check_opts=True, diff_opts=True ) @@ -65,13 +65,15 @@ def main(args): help="set additional key=value variables from the CLI") parser.add_option('-t', '--tags', dest='tags', default='all', help="only run plays and tasks tagged with these values") + parser.add_option('--skip-tags', dest='skip_tags', + help="only run plays and tasks whose tags do not match these values") parser.add_option('--syntax-check', dest='syntax', action='store_true', help="perform a syntax check on the playbook, but do not execute it") parser.add_option('--list-tasks', dest='listtasks', action='store_true', help="list all tasks that would be executed") parser.add_option('--step', dest='step', action='store_true', help="one-step-at-a-time: confirm each task before running") - parser.add_option('--start-at-task', dest='start_at', + parser.add_option('--start-at-task', dest='start_at', help="start the playbook at the task matching this name") options, args = parser.parse_args(args) @@ -97,6 +99,9 @@ def main(args): else: extra_vars = utils.parse_kv(options.extra_vars) only_tags = options.tags.split(",") + skip_tags = options.skip_tags + if options.skip_tags is not None: + skip_tags = options.skip_tags.split(",") for playbook in args: if not os.path.exists(playbook): @@ -136,6 +141,7 @@ def main(args): extra_vars=extra_vars, private_key_file=options.private_key_file, only_tags=only_tags, + skip_tags=skip_tags, check=options.check, diff=options.diff ) @@ -156,13 +162,20 @@ def main(args): print ' %s' % host if options.listtasks: 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) - (matched_tags | unmatched_tags) + unknown_tags = ((set(pb.only_tags) | set(pb.skip_tags)) - + (matched_tags | unmatched_tags)) + if unknown_tags: continue print ' play #%d (%s): task count=%d' % (playnum, label, len(play.tasks())) for task in play.tasks(): - if set(task.tags).intersection(pb.only_tags): + 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: # meta tasks have no names print ' %s' % task.name @@ -173,7 +186,7 @@ def main(args): # if we've not exited by now then we are fine. print 'Playbook Syntax is fine' return 0 - + failed_hosts = [] try: @@ -202,7 +215,7 @@ def main(args): colorize('ok', t['ok'], 'green'), colorize('changed', t['changed'], 'yellow'), colorize('unreachable', t['unreachable'], 'red'), - colorize('failed', t['failures'], 'red')), + colorize('failed', t['failures'], 'red')), screen_only=True ) @@ -211,11 +224,11 @@ def main(args): colorize('ok', t['ok'], None), colorize('changed', t['changed'], None), colorize('unreachable', t['unreachable'], None), - colorize('failed', t['failures'], None)), + colorize('failed', t['failures'], None)), log_only=True ) - + print "" if len(failed_hosts) > 0: return 2 diff --git a/lib/ansible/playbook/__init__.py b/lib/ansible/playbook/__init__.py index f554bd3e31..ec9d7fbb2a 100644 --- a/lib/ansible/playbook/__init__.py +++ b/lib/ansible/playbook/__init__.py @@ -63,6 +63,7 @@ class PlayBook(object): sudo_user = C.DEFAULT_SUDO_USER, extra_vars = None, only_tags = None, + skip_tags = None, subset = C.DEFAULT_SUBSET, inventory = None, check = False, @@ -97,6 +98,8 @@ class PlayBook(object): extra_vars = {} if only_tags is None: only_tags = [ 'all' ] + if skip_tags is None: + skip_tags = [] self.check = check self.diff = diff @@ -117,6 +120,7 @@ class PlayBook(object): self.global_vars = {} self.private_key_file = private_key_file self.only_tags = only_tags + self.skip_tags = skip_tags self.any_errors_fatal = any_errors_fatal self.callbacks.playbook = self @@ -216,11 +220,14 @@ class PlayBook(object): for (play_ds, play_basedir) in zip(self.playbook, self.play_basedirs): play = Play(self, play_ds, play_basedir) assert play is not None - + matched_tags, unmatched_tags = play.compare_tags(self.only_tags) matched_tags_all = matched_tags_all | matched_tags unmatched_tags_all = unmatched_tags_all | unmatched_tags + # Remove tasks we wish to skip + matched_tags = matched_tags - set(self.skip_tags) + # if we have matched_tags, the play must be run. # if the play contains no tasks, assume we just want to gather facts # in this case there are actually 3 meta tasks (handler flushes) not 0 @@ -228,9 +235,11 @@ class PlayBook(object): if (len(matched_tags) > 0 or len(play.tasks()) == 3): plays.append(play) - # if the playbook is invoked with --tags that don't exist at all in the playbooks - # then we need to raise an error so that the user can correct the arguments. - unknown_tags = set(self.only_tags) - (matched_tags_all | unmatched_tags_all) + # if the playbook is invoked with --tags or --skip-tags that don't + # exist at all in the playbooks then we need to raise an error so that + # the user can correct the arguments. + unknown_tags = ((set(self.only_tags) | set(self.skip_tags)) - + (matched_tags_all | unmatched_tags_all)) unknown_tags.discard('all') if len(unknown_tags) > 0: @@ -331,7 +340,7 @@ class PlayBook(object): ansible.callbacks.set_task(self.callbacks, None) ansible.callbacks.set_task(self.runner_callbacks, None) return True - + # load up an appropriate ansible runner to run the task in parallel results = self._run_task_internal(task) @@ -340,7 +349,7 @@ class PlayBook(object): if results is None: hosts_remaining = False results = {} - + contacted = results.get('contacted', {}) self.stats.compute(results, ignore_errors=task.ignore_errors) @@ -408,7 +417,7 @@ class PlayBook(object): self.callbacks.on_setup() self.inventory.restrict_to(host_list) - + ansible.callbacks.set_task(self.callbacks, None) ansible.callbacks.set_task(self.runner_callbacks, None) @@ -438,10 +447,10 @@ class PlayBook(object): def generate_retry_inventory(self, replay_hosts): ''' - called by /usr/bin/ansible when a playbook run fails. It generates a inventory + called by /usr/bin/ansible when a playbook run fails. It generates a inventory that allows re-running on ONLY the failed hosts. This may duplicate some variable information in group_vars/host_vars but that is ok, and expected. - ''' + ''' buf = StringIO.StringIO() for x in replay_hosts: @@ -507,7 +516,7 @@ class PlayBook(object): # meta tasks are an internalism and are not valid for end-user playbook usage # here a meta task is a placeholder that signals handlers should be run - + if task.meta == 'flush_handlers': for handler in play.handlers(): if len(handler.notified_by) > 0: @@ -530,11 +539,17 @@ class PlayBook(object): for x in self.only_tags: for y in task.tags: - if (x==y): + 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): # 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