From bed29b7e11eb33133700e1ee518f8cfdb640dc22 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Thu, 1 Mar 2012 20:41:17 -0500 Subject: [PATCH] Allow the /etc/ansible/hosts file to contain groups and those group names to be used in place of pattern names. --- lib/ansible/constants.py | 2 +- lib/ansible/playbook.py | 15 ++++++++------ lib/ansible/runner.py | 45 ++++++++++++++++++++++++++++++---------- 3 files changed, 44 insertions(+), 18 deletions(-) diff --git a/lib/ansible/constants.py b/lib/ansible/constants.py index 666a00ebf7d..2e55f0104b3 100644 --- a/lib/ansible/constants.py +++ b/lib/ansible/constants.py @@ -23,7 +23,7 @@ DEFAULT_MODULE_NAME = 'ping' DEFAULT_PATTERN = '*' DEFAULT_FORKS = 3 DEFAULT_MODULE_ARGS = '' -DEFAULT_TIMEOUT = 60 +DEFAULT_TIMEOUT = 10 DEFAULT_REMOTE_USER = 'root' DEFAULT_REMOTE_PASS = None diff --git a/lib/ansible/playbook.py b/lib/ansible/playbook.py index 33bcb755f79..a2e3998cacb 100755 --- a/lib/ansible/playbook.py +++ b/lib/ansible/playbook.py @@ -106,7 +106,7 @@ class PlayBook(object): # actions where not all hosts have changed # though top-level tasks will pass in "None" here host_list = self.host_list - host_list = ansible.runner.Runner.parse_hosts(host_list) + (host_list, groups) = ansible.runner.Runner.parse_hosts(host_list) # do not continue to run tasks on hosts that have had failures new_hosts = [] @@ -132,9 +132,9 @@ class PlayBook(object): if self.verbose: if not conditional: - print "\nTASK [%s]" % (name) + print "\nTASK: %s" % (name) else: - print "\nNOTIFIED [%s]" % (name) + print "\nNOTIFIED: %s" % (name) # load up an appropriate ansible runner to # run the task in parallel @@ -240,10 +240,12 @@ class PlayBook(object): tasks = pg['tasks'] handlers = pg['handlers'] user = pg.get('user', C.DEFAULT_REMOTE_USER) - self.host_list = pg.get('hosts', '/etc/ansible/hosts') + + host_file = pg.get('hosts', '/etc/ansible/hosts') + self.host_list, groups = ansible.runner.Runner.parse_hosts(host_file) if self.verbose: - print "PLAY: [%s] from [%s] ********** " % (pattern, self.host_list) + print "PLAY: [%s] ********** " % (pattern) # run all the top level tasks, these get run on every node @@ -252,7 +254,8 @@ class PlayBook(object): pattern=pattern, task=task, handlers=handlers, - remote_user=user) + remote_user=user + ) # handlers only run on certain nodes, they are flagged by _flag_handlers # above. They only run on nodes when things mark them as changed, and diff --git a/lib/ansible/runner.py b/lib/ansible/runner.py index e126494a85b..d71e53d2765 100755 --- a/lib/ansible/runner.py +++ b/lib/ansible/runner.py @@ -68,7 +68,8 @@ class Runner(object): ''' # save input values - self.host_list = self.parse_hosts(host_list) + + self.host_list, self.groups = self.parse_hosts(host_list) self.module_path = module_path self.module_name = module_name self.forks = forks @@ -78,19 +79,35 @@ class Runner(object): self.verbose = verbose self.remote_user = remote_user self.remote_pass = remote_pass - self._tmp_paths = {} + # hosts in each group name in the inventory file + self._tmp_paths = {} @classmethod def parse_hosts(cls, host_list): - ''' parse the host inventory file if not sent as an array ''' + ''' + parse the host inventory file, returns (hosts, groups) + [groupname] + host1 + host2 + ''' - # if the host list is given as a string load the host list - # from a file, one host per line - if type(host_list) != list: - host_list = os.path.expanduser(host_list) - return file(host_list).read().split("\n") + if type(host_list) == list: + return (host_list, {}) - return host_list + host_list = os.path.expanduser(host_list) + lines = file(host_list).read().split("\n") + groups = {} + group_name = 'ungrouped' + results = [] + for item in lines: + if item.startswith("["): + group_name = item.replace("[","").replace("]","").lstrip().rstrip() + groups[group_name] = [] + else: + groups[group_name].append(item) + results.append(item) + + return (results, groups) def _matches(self, host_name, pattern=None): @@ -98,12 +115,18 @@ class Runner(object): # a pattern is in fnmatch format but more than one pattern # can be strung together with semicolons. ex: # atlanta-web*.example.com;dc-web*.example.com + if host_name == '': return False subpatterns = pattern.split(";") for subpattern in subpatterns: + # the pattern could be a real glob if fnmatch.fnmatch(host_name, subpattern): return True + # or it could be a literal group name instead + if self.groups.has_key(subpattern): + if host_name in self.groups[subpattern]: + return True return False def _connect(self, host): @@ -117,7 +140,7 @@ class Runner(object): try: # try paramiko ssh.connect(host, username=self.remote_user, allow_agent=True, - look_for_keys=True, password=self.remote_pass timeout=self.timeout) + look_for_keys=True, password=self.remote_pass, timeout=self.timeout) return [ True, ssh ] except Exception, e: # it failed somehow, return the failure string @@ -303,7 +326,7 @@ class Runner(object): # find hosts that match the pattern hosts = self.match_hosts(self.pattern) - + # attack pool of hosts in N forks # _executor_hook does all of the work hosts = [ (self,x) for x in hosts ]