bin/ansible-playbook
01e51b12
 #!/usr/bin/env python
7de661dd
 # (C) 2012, Michael DeHaan, <michael.dehaan@gmail.com>
 
 # This file is part of Ansible
 #
 # Ansible is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
 # the Free Software Foundation, either version 3 of the License, or
 # (at your option) any later version.
 #
 # Ansible is distributed in the hope that it will be useful,
 # but WITHOUT ANY WARRANTY; without even the implied warranty of
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 # GNU General Public License for more details.
 #
 # You should have received a copy of the GNU General Public License
 # along with Ansible.  If not, see <http://www.gnu.org/licenses/>.
c57df622
 
 #######################################################
7de661dd
 
 import sys
7256c5af
 import os
7de661dd
 
a4b8cdf8
 import ansible.playbook
 import ansible.constants as C
04349ec3
 import ansible.utils.template
a4b8cdf8
 from ansible import errors
7ed734df
 from ansible import callbacks
7e50d170
 from ansible import utils
12ff9b5b
 from ansible.color import ANSIBLE_COLOR, stringc
aa552685
 from ansible.callbacks import display
bc17553d
 
 def colorize(lead, num, color):
12ff9b5b
     """ Print 'lead' = 'num' in 'color' """
aa552685
     if num != 0 and ANSIBLE_COLOR and color is not None:
bc17553d
         return "%s%s%-15s" % (stringc(lead, color), stringc("=", color), stringc(str(num), color))
     else:
         return "%s=%-4s" % (lead, str(num))
 
aa552685
 def hostcolor(host, stats, color=True):
     if ANSIBLE_COLOR and color:
12ff9b5b
         if stats['failures'] != 0 or stats['unreachable'] != 0:
bc0be456
             return "%-37s" % stringc(host, 'red')
12ff9b5b
         elif stats['changed'] != 0:
bc0be456
             return "%-37s" % stringc(host, 'yellow')
12ff9b5b
         else:
bc0be456
             return "%-37s" % stringc(host, 'green')
     return "%-26s" % host
bc17553d
 
 
7de661dd
 def main(args):
c57df622
     ''' run ansible-playbook operations '''
 
     # create parser for CLI options
a9162a86
     parser = utils.base_parser(
04349ec3
         constants=C,
dc984d94
         usage = "%prog playbook.yml",
04349ec3
         connect_opts=True,
         runas_opts=True,
         subset_opts=True,
a9162a86
         check_opts=True,
         diff_opts=True
     )
21d2069a
     parser.add_option('-e', '--extra-vars', dest="extra_vars", action="append",
8c9f84f6
         help="set additional variables as key=value or YAML/JSON", default=[])
83f23ef8
     parser.add_option('-t', '--tags', dest='tags', default='all',
         help="only run plays and tasks tagged with these values")
04349ec3
     parser.add_option('--skip-tags', dest='skip_tags',
         help="only run plays and tasks whose tags do not match these values")
41195d0d
     parser.add_option('--syntax-check', dest='syntax', action='store_true',
dc984d94
         help="perform a syntax check on the playbook, but do not execute it")
1205bbe1
     parser.add_option('--list-tasks', dest='listtasks', action='store_true',
dc984d94
         help="list all tasks that would be executed")
7687c2ca
     parser.add_option('--step', dest='step', action='store_true',
         help="one-step-at-a-time: confirm each task before running")
04349ec3
     parser.add_option('--start-at-task', dest='start_at',
dc984d94
         help="start the playbook at the task matching this name")
8ae71cc7
 
7de661dd
     options, args = parser.parse_args(args)
 
     if len(args) == 0:
5a4d4bc0
         parser.print_help(file=sys.stderr)
cbfabcd0
         return 1
 
a259b955
     inventory = ansible.inventory.Inventory(options.inventory)
     inventory.subset(options.subset)
     if len(inventory.list_hosts()) == 0:
         raise errors.AnsibleError("provided hosts list is empty")
 
cbfabcd0
     sshpass = None
f2465e05
     sudopass = None
1205bbe1
     if not options.listhosts and not options.syntax and not options.listtasks:
f436a8c8
         options.ask_pass = options.ask_pass or C.DEFAULT_ASK_PASS
5a47953e
         # Never ask for an SSH password when we run with local connection
         if options.connection == "local":
0988a866
             options.ask_pass = False
604bf9f5
         options.ask_sudo_pass = options.ask_sudo_pass or C.DEFAULT_ASK_SUDO_PASS
0988a866
         (sshpass, sudopass) = utils.ask_passwords(ask_pass=options.ask_pass, ask_sudo_pass=options.ask_sudo_pass)
8e039a63
         options.sudo_user = options.sudo_user or C.DEFAULT_SUDO_USER
c19c2c72
 
     extra_vars = {}
21d2069a
     for extra_vars_opt in options.extra_vars:
         if extra_vars_opt.startswith("@"):
8c9f84f6
             # Argument is a YAML file (JSON is a subset of YAML)
             extra_vars = utils.combine_vars(extra_vars, utils.parse_yaml_from_file(extra_vars_opt[1:]))
ebd8e262
         elif extra_vars_opt and extra_vars_opt[0] in '[{':
8c9f84f6
             # Arguments as YAML
             extra_vars = utils.combine_vars(extra_vars, utils.parse_yaml(extra_vars_opt))
c19c2c72
         else:
             # Arguments as Key-value
21d2069a
             extra_vars = utils.combine_vars(extra_vars, utils.parse_kv(extra_vars_opt))
c19c2c72
 
83f23ef8
     only_tags = options.tags.split(",")
04349ec3
     skip_tags = options.skip_tags
     if options.skip_tags is not None:
         skip_tags = options.skip_tags.split(",")
7de661dd
 
7256c5af
     for playbook in args:
         if not os.path.exists(playbook):
             raise errors.AnsibleError("the playbook: %s could not be found" % playbook)
         if not os.path.isfile(playbook):
             raise errors.AnsibleError("the playbook: %s does not appear to be a file" % playbook)
 
c57df622
     # run all playbooks specified on the command line
7de661dd
     for playbook in args:
6dda6f12
 
6cd3ba5b
         # let inventory know which playbooks are using so it can know the basedirs
         inventory.set_playbook_basedir(os.path.dirname(playbook))
 
6dda6f12
         stats = callbacks.AggregateStats()
846186e2
         playbook_cb = callbacks.PlaybookCallbacks(verbose=utils.VERBOSITY)
7687c2ca
         if options.step:
             playbook_cb.step = options.step
690738ea
         if options.start_at:
             playbook_cb.start_at = options.start_at
846186e2
         runner_cb = callbacks.PlaybookRunnerCallbacks(stats, verbose=utils.VERBOSITY)
6dda6f12
 
7de661dd
         pb = ansible.playbook.PlayBook(
35fdf663
             playbook=playbook,
             module_path=options.module_path,
900790af
             inventory=inventory,
faed4b5a
             forks=options.forks,
35fdf663
             remote_user=options.remote_user,
faed4b5a
             remote_pass=sshpass,
             callbacks=playbook_cb,
             runner_callbacks=runner_cb,
35fdf663
             stats=stats,
faed4b5a
             timeout=options.timeout,
35fdf663
             transport=options.connection,
             sudo=options.sudo,
710d085d
             sudo_user=options.sudo_user,
b9982fc1
             sudo_pass=sudopass,
b42628d8
             extra_vars=extra_vars,
83f23ef8
             private_key_file=options.private_key_file,
             only_tags=only_tags,
04349ec3
             skip_tags=skip_tags,
a9162a86
             check=options.check,
             diff=options.diff
7de661dd
         )
1205bbe1
 
         if options.listhosts or options.listtasks:
e564de39
             print ''
9ea26c75
             print 'playbook: %s' % playbook
e564de39
             print ''
8e039a63
             playnum = 0
9a34c20c
             for (play_ds, play_basedir) in zip(pb.playbook, pb.play_basedirs):
8e039a63
                 playnum += 1
9a34c20c
                 play = ansible.playbook.Play(pb, play_ds, play_basedir)
                 label = play.name
1205bbe1
                 if options.listhosts:
                     hosts = pb.inventory.list_hosts(play.hosts)
e564de39
                     print '  play #%d (%s): host count=%d' % (playnum, label, len(hosts))
1205bbe1
                     for host in hosts:
                         print '    %s' % host
                 if options.listtasks:
                     matched_tags, unmatched_tags = play.compare_tags(pb.only_tags)
04349ec3
 
                     # Remove skipped tasks
                     matched_tags = matched_tags - set(pb.skip_tags)
 
1205bbe1
                     unmatched_tags.discard('all')
04349ec3
                     unknown_tags = ((set(pb.only_tags) | set(pb.skip_tags)) -
                                     (matched_tags | unmatched_tags))
 
1205bbe1
                     if unknown_tags:
89ab3a0b
                         continue
d8e5fc9d
                     print '  play #%d (%s):' % (playnum, label)
 
1205bbe1
                     for task in play.tasks():
04349ec3
                         if (set(task.tags).intersection(pb.only_tags) and not
                             set(task.tags).intersection(pb.skip_tags)):
89ab3a0b
                             if getattr(task, 'name', None) is not None:
                                 # meta tasks have no names
                                 print '    %s' % task.name
e564de39
                 print ''
9ea26c75
             continue
41195d0d
 
         if options.syntax:
             # if we've not exited by now then we are fine.
             print 'Playbook Syntax is fine'
             return 0
04349ec3
 
c695aa2d
         failed_hosts = []
2b96c347
         unreachable_hosts = []
a2f76c1c
 
dfd2c6dc
         try:
6dda6f12
 
f074f1c4
             pb.run()
a2f76c1c
 
6dda6f12
             hosts = sorted(pb.stats.processed.keys())
aa552685
             display(callbacks.banner("PLAY RECAP"))
3017dc92
             playbook_cb.on_stats(pb.stats)
c695aa2d
 
             for h in hosts:
                 t = pb.stats.summarize(h)
2b96c347
                 if t['failures'] > 0:
c695aa2d
                     failed_hosts.append(h)
2b96c347
                 if t['unreachable'] > 0:
                     unreachable_hosts.append(h)
c695aa2d
 
2b96c347
             retries = failed_hosts + unreachable_hosts
 
             if len(retries) > 0:
                 filename = pb.generate_retry_inventory(retries)
c695aa2d
                 if filename:
aa552685
                     display("           to retry, use: --limit @%s\n" % filename)
c695aa2d
 
6dda6f12
             for h in hosts:
                 t = pb.stats.summarize(h)
aa552685
 
                 display("%s : %s %s %s %s" % (
8e039a63
                     hostcolor(h, t),
                     colorize('ok', t['ok'], 'green'),
                     colorize('changed', t['changed'], 'yellow'),
                     colorize('unreachable', t['unreachable'], 'red'),
04349ec3
                     colorize('failed', t['failures'], 'red')),
aa552685
                     screen_only=True
                 )
 
                 display("%s : %s %s %s %s" % (
                     hostcolor(h, t, False),
                     colorize('ok', t['ok'], None),
                     colorize('changed', t['changed'], None),
                     colorize('unreachable', t['unreachable'], None),
04349ec3
                     colorize('failed', t['failures'], None)),
aa552685
                     log_only=True
                 )
b09ef21e
 
04349ec3
 
c695aa2d
             print ""
             if len(failed_hosts) > 0:
                 return 2
2b96c347
             if len(unreachable_hosts) > 0:
                 return 3
6dda6f12
 
a5f4ca50
         except errors.AnsibleError, e:
aa552685
             display("ERROR: %s" % e, color='red')
dfd2c6dc
             return 1
7de661dd
 
     return 0
 
 
 if __name__ == "__main__":
aa552685
     display(" ", log_only=True)
     display(" ".join(sys.argv), log_only=True)
     display(" ", log_only=True)
a5f4ca50
     try:
         sys.exit(main(sys.argv[1:]))
     except errors.AnsibleError, e:
aa552685
         display("ERROR: %s" % e, color='red', stderr=True)
a5f4ca50
         sys.exit(1)