def _parse(self): all_hosts = {} self.raw = utils.parse_json(self.data) all=Group('all') groups = dict(all=all) group = None for (group_name, data) in self.raw.items(): group = groups[group_name] = Group(group_name) host = None if not isinstance(data, dict): data = {'hosts': data} if 'hosts' in data: for hostname in data['hosts']: if not hostname in all_hosts: all_hosts[hostname] = Host(hostname) host = all_hosts[hostname] group.add_host(host) if 'vars' in data: for k, v in data['vars'].iteritems(): group.set_variable(k, v) all.add_child_group(group) # Separate loop to ensure all groups are defined for (group_name, data) in self.raw.items(): if isinstance(data, dict) and 'children' in data: for child_name in data['children']: if child_name in groups: groups[group_name].add_child_group(groups[child_name]) return groups
def _parse(self, err): all_hosts = {} self.raw = utils.parse_json(self.data) all = Group('all') groups = dict(all=all) group = None if 'failed' in self.raw: sys.stderr.write(err + "\n") raise errors.AnsibleError("failed to parse executable inventory script results: %s" % self.raw) for (group_name, data) in self.raw.items(): # in Ansible 1.3 and later, a "_meta" subelement may contain # a variable "hostvars" which contains a hash for each host # if this "hostvars" exists at all then do not call --host for each # host. This is for efficiency and scripts should still return data # if called with --host for backwards compat with 1.2 and earlier. if group_name == '_meta': if 'hostvars' in data: self.host_vars_from_top = data['hostvars'] continue group = groups[group_name] = Group(group_name) host = None if not isinstance(data, dict): data = {'hosts': data} elif not any(k in data for k in ('hosts','vars')): data = {'hosts': [group_name], 'vars': data} if 'hosts' in data: for hostname in data['hosts']: if not hostname in all_hosts: all_hosts[hostname] = Host(hostname) host = all_hosts[hostname] group.add_host(host) if 'vars' in data: for k, v in data['vars'].iteritems(): if group.name == all.name: all.set_variable(k, v) else: group.set_variable(k, v) if group.name != all.name: all.add_child_group(group) # Separate loop to ensure all groups are defined for (group_name, data) in self.raw.items(): if group_name == '_meta': continue if isinstance(data, dict) and 'children' in data: for child_name in data['children']: if child_name in groups: groups[group_name].add_child_group(groups[child_name]) return groups
def parse_inventory(self, host_list): if isinstance(host_list, string_types): if "," in host_list: host_list = host_list.split(",") host_list = [h for h in host_list if h and h.strip()] self.parser = None # Always create the 'all' and 'ungrouped' groups, even if host_list is # empty: in this case we will subsequently an the implicit 'localhost' to it. ungrouped = Group(name='ungrouped') all = Group('all') all.add_child_group(ungrouped) self.groups = dict(all=all, ungrouped=ungrouped) if host_list is None: pass elif isinstance(host_list, list): for h in host_list: try: (host, port) = parse_address(h, allow_ranges=False) except AnsibleError as e: display.vvv( "Unable to parse address from hostname, leaving unchanged: %s" % to_unicode(e)) host = h port = None all.add_host(Host(host, port)) elif self._loader.path_exists(host_list): #TODO: switch this to a plugin loader and a 'condition' per plugin on which it should be tried, restoring 'inventory pllugins' if self.is_directory(host_list): # Ensure basedir is inside the directory host_list = os.path.join(self.host_list, "") self.parser = InventoryDirectory(loader=self._loader, groups=self.groups, filename=host_list) else: self.parser = get_file_parser(host_list, self.groups, self._loader) vars_loader.add_directory(self.basedir(), with_subdir=True) if not self.parser: # should never happen, but JIC raise AnsibleError( "Unable to parse %s as an inventory source" % host_list) self._vars_plugins = [x for x in vars_loader.all(self)] # get group vars from group_vars/ files and vars plugins for group in self.groups.values(): group.vars = combine_vars(group.vars, self.get_group_variables(group.name)) # get host vars from host_vars/ files and vars plugins for host in self.get_hosts(): host.vars = combine_vars(host.vars, self.get_host_variables(host.name))
def _parse_base_groups(self): # FIXME: refactor ungrouped = Group(name='ungrouped') all = Group(name='all') all.add_child_group(ungrouped) self.groups = dict(all=all, ungrouped=ungrouped) active_group_name = 'ungrouped' for line in self.lines: if line.startswith("["): active_group_name = line.replace("[","").replace("]","").strip() if line.find(":vars") != -1 or line.find(":children") != -1: active_group_name = None else: new_group = self.groups[active_group_name] = Group(name=active_group_name) all.add_child_group(new_group) elif line.startswith("#") or line == '': pass elif active_group_name: tokens = shlex.split(line) if len(tokens) == 0: continue hostname = tokens[0] port = C.DEFAULT_REMOTE_PORT # Two cases to check: # 0. A hostname that contains a range pesudo-code and a port # 1. A hostname that contains just a port if (hostname.find("[") != -1 and hostname.find("]") != -1 and hostname.find(":") != -1 and (hostname.rindex("]") < hostname.rindex(":")) or (hostname.find("]") == -1 and hostname.find(":") != -1)): tokens2 = hostname.rsplit(":", 1) hostname = tokens2[0] port = tokens2[1] host = None _all_hosts = [] if hostname in self.hosts: host = self.hosts[hostname] _all_hosts.append(host) else: if detect_range(hostname): _hosts = expand_hostname_range(hostname) for _ in _hosts: host = Host(name=_, port=port) self.hosts[_] = host _all_hosts.append(host) else: host = Host(name=hostname, port=port) self.hosts[hostname] = host _all_hosts.append(host) if len(tokens) > 1: for t in tokens[1:]: (k,v) = t.split("=") host.set_variable(k,v) for _ in _all_hosts: self.groups[active_group_name].add_host(_)
def test_depth_update(self): A = Group('A') B = Group('B') Z = Group('Z') A.add_child_group(B) A.add_child_group(Z) self.assertEqual(A.depth, 0) self.assertEqual(Z.depth, 1) self.assertEqual(B.depth, 1)
def test_depth_recursion(self): A = Group('A') B = Group('B') A.add_child_group(B) # hypothetical of adding B as child group to A A.parent_groups.append(B) B.child_groups.append(A) # can't update depths of groups, because of loop with self.assertRaises(AnsibleError): B._check_children_depth()
def parse_inventory(self, host_list): if isinstance(host_list, string_types): if "," in host_list: host_list = host_list.split(",") host_list = [ h for h in host_list if h and h.strip() ] self.parser = None # Always create the 'all' and 'ungrouped' groups, even if host_list is # empty: in this case we will subsequently an the implicit 'localhost' to it. ungrouped = Group('ungrouped') all = Group('all') all.add_child_group(ungrouped) self.groups = dict(all=all, ungrouped=ungrouped) if host_list is None: pass elif isinstance(host_list, list): for h in host_list: try: (host, port) = parse_address(h, allow_ranges=False) except AnsibleError as e: display.vvv("Unable to parse address from hostname, leaving unchanged: %s" % to_unicode(e)) host = h port = None all.add_host(Host(host, port)) elif self._loader.path_exists(host_list): #TODO: switch this to a plugin loader and a 'condition' per plugin on which it should be tried, restoring 'inventory pllugins' if self.is_directory(host_list): # Ensure basedir is inside the directory host_list = os.path.join(self.host_list, "") self.parser = InventoryDirectory(loader=self._loader, groups=self.groups, filename=host_list) else: self.parser = get_file_parser(host_list, self.groups, self._loader) vars_loader.add_directory(self.basedir(), with_subdir=True) if not self.parser: # should never happen, but JIC raise AnsibleError("Unable to parse %s as an inventory source" % host_list) else: display.warning("Host file not found: %s" % to_unicode(host_list)) self._vars_plugins = [ x for x in vars_loader.all(self) ] # set group vars from group_vars/ files and vars plugins for g in self.groups: group = self.groups[g] group.vars = combine_vars(group.vars, self.get_group_variables(group.name)) # set host vars from host_vars/ files and vars plugins for host in self.get_hosts(): host.vars = combine_vars(host.vars, self.get_host_variables(host.name))
def test_get_group_vars_with_nested_groups(self): parent_group = Group('some_parent_group') parent_group.set_variable('parent_some_key', 'parent_some_value') child_group = Group('some_child_group') child_group.set_variable('child_some_key', 'child_some_value') parent_group.add_child_group(child_group) self.hostA.add_group(child_group) group_vars = self.hostA.get_group_vars() self.assertIsInstance(group_vars, dict) self.assertIn('parent_some_key', group_vars) self.assertIn('child_some_key', group_vars)
def test_sub_group_host_ordering(self): """With multiple nested groups, asserts that hosts are returned in deterministic order """ top_group = Group('A') expected_hosts = [] for name in ['z', 'b', 'c', 'a', 'p', 'q']: child = Group('group_{0}'.format(name)) top_group.add_child_group(child) host = Host('host_{0}'.format(name)) child.add_host(host) expected_hosts.append(host) assert top_group.get_hosts() == expected_hosts
def parse_inventory(self, host_list): if isinstance(host_list, string_types): if "," in host_list: host_list = host_list.split(",") host_list = [ h for h in host_list if h and h.strip() ] self.parser = None # Always create the 'all' and 'ungrouped' groups, even if host_list is # empty: in this case we will subsequently an the implicit 'localhost' to it. ungrouped = Group(name='ungrouped') all = Group('all') all.add_child_group(ungrouped) self.groups = dict(all=all, ungrouped=ungrouped) if host_list is None: pass elif isinstance(host_list, list): for h in host_list: (host, port) = parse_address(h, allow_ranges=False) all.add_host(Host(host, port)) elif self._loader.path_exists(host_list): #TODO: switch this to a plugin loader and a 'condition' per plugin on which it should be tried, restoring 'inventory pllugins' if self._loader.is_directory(host_list): # Ensure basedir is inside the directory host_list = os.path.join(self.host_list, "") self.parser = InventoryDirectory(loader=self._loader, groups=self.groups, filename=host_list) else: self.parser = get_file_parser(host_list, self.groups, self._loader) vars_loader.add_directory(self.basedir(), with_subdir=True) if not self.parser: # should never happen, but JIC raise AnsibleError("Unable to parse %s as an inventory source" % host_list) self._vars_plugins = [ x for x in vars_loader.all(self) ] # FIXME: shouldn't be required, since the group/host vars file # management will be done in VariableManager # get group vars from group_vars/ files and vars plugins for group in self.groups.values(): group.vars = combine_vars(group.vars, self.get_group_variables(group.name)) # get host vars from host_vars/ files and vars plugins for host in self.get_hosts(): host.vars = combine_vars(host.vars, self.get_host_variables(host.name))
def _parse(self, err): all_hosts = {} self.raw = utils.parse_json(self.data) all = Group('all') groups = dict(all=all) group = None if 'failed' in self.raw: sys.stderr.write(err + "\n") raise errors.AnsibleError( "failed to parse executable inventory script results: %s" % self.raw) for (group_name, data) in self.raw.items(): group = groups[group_name] = Group(group_name) host = None if not isinstance(data, dict): data = {'hosts': data} elif not any(k in data for k in ('hosts', 'vars')): data = {'hosts': [group_name], 'vars': data} if 'hosts' in data: for hostname in data['hosts']: if not hostname in all_hosts: all_hosts[hostname] = Host(hostname) host = all_hosts[hostname] group.add_host(host) if 'vars' in data: for k, v in data['vars'].iteritems(): if group.name == all.name: all.set_variable(k, v) else: group.set_variable(k, v) if group.name != all.name: all.add_child_group(group) # Separate loop to ensure all groups are defined for (group_name, data) in self.raw.items(): if isinstance(data, dict) and 'children' in data: for child_name in data['children']: if child_name in groups: groups[group_name].add_child_group(groups[child_name]) return groups
def _parse(self): groups = {} self.raw = utils.parse_json(self.data) all = Group('all') self.groups = dict(all=all) group = None for (group_name, hosts) in self.raw.items(): group = groups[group_name] = Group(group_name) host = None for hostname in hosts: host = Host(hostname) group.add_host(host) # FIXME: hack shouldn't be needed all.add_host(host) all.add_child_group(group) return groups
def _parse(self): groups = {} self.raw = utils.parse_json(self.data) all=Group('all') self.groups = dict(all=all) group = None for (group_name, hosts) in self.raw.items(): group = groups[group_name] = Group(group_name) host = None for hostname in hosts: host = Host(hostname) group.add_host(host) # FIXME: hack shouldn't be needed all.add_host(host) all.add_child_group(group) return groups
def _parse_base_groups(self): ungrouped = Group(name='ungrouped') all = Group(name='all') all.add_child_group(ungrouped) self.groups = dict(all=all, ungrouped=ungrouped) active_group_name = 'ungrouped' for line in self.lines: if line.startswith("["): active_group_name = line.replace("[", "").replace("]", "").strip() if line.find(":vars") != -1 or line.find(":children") != -1: active_group_name = None else: new_group = self.groups[active_group_name] = Group( name=active_group_name) all.add_child_group(new_group) elif line.startswith("#") or line == '': pass elif active_group_name: tokens = line.split() if len(tokens) == 0: continue hostname = tokens[0] port = C.DEFAULT_REMOTE_PORT if hostname.find(":") != -1: tokens2 = hostname.split(":") hostname = tokens2[0] port = tokens2[1] host = None if hostname in self.hosts: host = self.hosts[hostname] else: host = Host(name=hostname, port=port) self.hosts[hostname] = host if len(tokens) > 1: for t in tokens[1:]: (k, v) = t.split("=") host.set_variable(k, v) self.groups[active_group_name].add_host(host)
def get_ansible_groups(group_map): """ Constructs a list of :class:`ansible.inventory.group.Group` objects from a map of lists of host strings. """ # Some of this logic is cribbed from # ansible.inventory.script.InventoryScript all_hosts = {} group_all = Group('all') groups = [group_all] for gname, hosts in group_map.iteritems(): g = Group(gname) for host in hosts: h = all_hosts.get(host, Host(host)) all_hosts[host] = h g.add_host(h) group_all.add_host(h) group_all.add_child_group(g) groups.append(g) return groups
def _parse_base_groups(self): ungrouped = Group(name='ungrouped') all = Group(name='all') all.add_child_group(ungrouped) self.groups = dict(all=all, ungrouped=ungrouped) active_group_name = 'ungrouped' for line in self.lines: if line.startswith("["): active_group_name = line.replace("[","").replace("]","").strip() if line.find(":vars") != -1 or line.find(":children") != -1: active_group_name = None else: new_group = self.groups[active_group_name] = Group(name=active_group_name) all.add_child_group(new_group) elif line.startswith("#") or line == '': pass elif active_group_name: tokens = line.split() if len(tokens) == 0: continue hostname = tokens[0] port = C.DEFAULT_REMOTE_PORT if hostname.find(":") != -1: tokens2 = hostname.split(":") hostname = tokens2[0] port = tokens2[1] host = None if hostname in self.hosts: host = self.hosts[hostname] else: host = Host(name=hostname, port=port) self.hosts[hostname] = host if len(tokens) > 1: for t in tokens[1:]: (k,v) = t.split("=") host.set_variable(k,v) self.groups[active_group_name].add_host(host)
def test_loop_detection(self): A = Group('A') B = Group('B') C = Group('C') A.add_child_group(B) B.add_child_group(C) with self.assertRaises(AnsibleError): C.add_child_group(A)
def test_populates_descendant_hosts(self): A = Group('A') B = Group('B') C = Group('C') h = Host('h') C.add_host(h) A.add_child_group(B) # B is child of A B.add_child_group(C) # C is descendant of A A.add_child_group(B) self.assertEqual(set(h.groups), set([C, B, A])) h2 = Host('h2') C.add_host(h2) self.assertEqual(set(h2.groups), set([C, B, A]))
def parse_inventory(self, host_list): if isinstance(host_list, string_types): if "," in host_list: host_list = host_list.split(",") host_list = [ h for h in host_list if h and h.strip() ] self.parser = None # Always create the 'all' and 'ungrouped' groups, even if host_list is # empty: in this case we will subsequently an the implicit 'localhost' to it. ungrouped = Group('ungrouped') all = Group('all') all.add_child_group(ungrouped) self.groups = dict(all=all, ungrouped=ungrouped) if host_list is None: pass elif isinstance(host_list, list): for h in host_list: try: (host, port) = parse_address(h, allow_ranges=False) except AnsibleError as e: display.vvv("Unable to parse address from hostname, leaving unchanged: %s" % to_text(e)) host = h port = None new_host = Host(host, port) if h in C.LOCALHOST: # set default localhost from inventory to avoid creating an implicit one. Last localhost defined 'wins'. if self.localhost is not None: display.warning("A duplicate localhost-like entry was found (%s). First found localhost was %s" % (h, self.localhost.name)) display.vvvv("Set default localhost to %s" % h) self.localhost = new_host all.add_host(new_host) elif self._loader.path_exists(host_list): # TODO: switch this to a plugin loader and a 'condition' per plugin on which it should be tried, restoring 'inventory pllugins' if self.is_directory(host_list): # Ensure basedir is inside the directory host_list = os.path.join(self.host_list, "") self.parser = InventoryDirectory(loader=self._loader, groups=self.groups, filename=host_list) else: self.parser = get_file_parser(host_list, self.groups, self._loader) vars_loader.add_directory(self._basedir, with_subdir=True) if not self.parser: # should never happen, but JIC raise AnsibleError("Unable to parse %s as an inventory source" % host_list) else: display.warning("Host file not found: %s" % to_text(host_list)) self._vars_plugins = [ x for x in vars_loader.all(self) ] # set group vars from group_vars/ files and vars plugins for g in self.groups: group = self.groups[g] group.vars = combine_vars(group.vars, self.get_group_variables(group.name)) self.get_group_vars(group) # get host vars from host_vars/ files and vars plugins for host in self.get_hosts(ignore_limits=True, ignore_restrictions=True): host.vars = combine_vars(host.vars, self.get_host_variables(host.name)) self.get_host_vars(host)
def _parse(self, data): all = Group('all') ungrouped = Group('ungrouped') all.add_child_group(ungrouped) self.groups = dict(all=all, ungrouped=ungrouped) grouped_hosts = [] yaml = utils.parse_yaml(data) # first add all groups for item in yaml: if type(item) == dict and 'group' in item: group = Group(item['group']) for subresult in item.get('hosts',[]): if type(subresult) in [ str, unicode ]: host = self._make_host(subresult) group.add_host(host) grouped_hosts.append(host) elif type(subresult) == dict: host = self._make_host(subresult['host']) vars = subresult.get('vars',{}) if type(vars) == list: for subitem in vars: for (k,v) in subitem.items(): host.set_variable(k,v) elif type(vars) == dict: for (k,v) in subresult.get('vars',{}).items(): host.set_variable(k,v) else: raise errors.AnsibleError("unexpected type for variable") group.add_host(host) grouped_hosts.append(host) vars = item.get('vars',{}) if type(vars) == dict: for (k,v) in item.get('vars',{}).items(): group.set_variable(k,v) elif type(vars) == list: for subitem in vars: if type(subitem) != dict: raise errors.AnsibleError("expected a dictionary") for (k,v) in subitem.items(): group.set_variable(k,v) self.groups[group.name] = group all.add_child_group(group) # add host definitions for item in yaml: if type(item) in [ str, unicode ]: host = self._make_host(item) if host not in grouped_hosts: ungrouped.add_host(host) elif type(item) == dict and 'host' in item: host = self._make_host(item['host']) vars = item.get('vars', {}) if type(vars)==list: varlist, vars = vars, {} for subitem in varlist: vars.update(subitem) for (k,v) in vars.items(): host.set_variable(k,v) if host not in grouped_hosts: ungrouped.add_host(host)
def _parse_base_groups(self): # FIXME: refactor ungrouped = Group(name='ungrouped') all = Group(name='all') all.add_child_group(ungrouped) self.groups = dict(all=all, ungrouped=ungrouped) active_group_name = 'ungrouped' for line in self.lines: line = self._before_comment(line).strip() if line.startswith("[") and line.endswith("]"): active_group_name = line.replace("[","").replace("]","") if ":vars" in line or ":children" in line: active_group_name = active_group_name.rsplit(":", 1)[0] if active_group_name not in self.groups: new_group = self.groups[active_group_name] = Group(name=active_group_name) active_group_name = None elif active_group_name not in self.groups: new_group = self.groups[active_group_name] = Group(name=active_group_name) elif line.startswith(";") or line == '': pass elif active_group_name: tokens = shlex.split(line) if len(tokens) == 0: continue hostname = tokens[0] port = C.DEFAULT_REMOTE_PORT # Three cases to check: # 0. A hostname that contains a range pesudo-code and a port # 1. A hostname that contains just a port if hostname.count(":") > 1: # Possible an IPv6 address, or maybe a host line with multiple ranges # IPv6 with Port XXX:XXX::XXX.port # FQDN foo.example.com if hostname.count(".") == 1: (hostname, port) = hostname.rsplit(".", 1) elif ("[" in hostname and "]" in hostname and ":" in hostname and (hostname.rindex("]") < hostname.rindex(":")) or ("]" not in hostname and ":" in hostname)): (hostname, port) = hostname.rsplit(":", 1) hostnames = [] if detect_range(hostname): hostnames = expand_hostname_range(hostname) else: hostnames = [hostname] for hn in hostnames: host = None if hn in self.hosts: host = self.hosts[hn] else: host = Host(name=hn, port=port) self.hosts[hn] = host if len(tokens) > 1: for t in tokens[1:]: if t.startswith('#'): break try: (k,v) = t.split("=", 1) except ValueError, e: raise AnsibleError("Invalid ini entry in %s: %s - %s" % (self.filename, t, str(e))) if k == 'ansible_ssh_host': host.ipv4_address = self._parse_value(v) else: host.set_variable(k, self._parse_value(v)) self.groups[active_group_name].add_host(host)
def _parse(self, err): all_hosts = {} # not passing from_remote because data from CMDB is trusted self.raw = utils.parse_json(self.data) self.raw = json_dict_bytes_to_unicode(self.raw) all = Group('all') groups = dict(all=all) group = None if 'failed' in self.raw: sys.stderr.write(err + "\n") raise errors.AnsibleError("failed to parse executable inventory script results: %s" % self.raw) for (group_name, data) in self.raw.items(): # in Ansible 1.3 and later, a "_meta" subelement may contain # a variable "hostvars" which contains a hash for each host # if this "hostvars" exists at all then do not call --host for each # host. This is for efficiency and scripts should still return data # if called with --host for backwards compat with 1.2 and earlier. if group_name == '_meta': if 'hostvars' in data: self.host_vars_from_top = data['hostvars'] continue if group_name != all.name: group = groups[group_name] = Group(group_name) else: group = all host = None if not isinstance(data, dict): data = {'hosts': data} # is not those subkeys, then simplified syntax, host with vars elif not any(k in data for k in ('hosts','vars')): data = {'hosts': [group_name], 'vars': data} if 'hosts' in data: if not isinstance(data['hosts'], list): raise errors.AnsibleError("You defined a group \"%s\" with bad " "data for the host list:\n %s" % (group_name, data)) for hostname in data['hosts']: if not hostname in all_hosts: all_hosts[hostname] = Host(hostname) host = all_hosts[hostname] group.add_host(host) if 'vars' in data: if not isinstance(data['vars'], dict): raise errors.AnsibleError("You defined a group \"%s\" with bad " "data for variables:\n %s" % (group_name, data)) for k, v in data['vars'].iteritems(): if group.name == all.name: all.set_variable(k, v) else: group.set_variable(k, v) # Separate loop to ensure all groups are defined for (group_name, data) in self.raw.items(): if group_name == '_meta': continue if isinstance(data, dict) and 'children' in data: for child_name in data['children']: if child_name in groups: groups[group_name].add_child_group(groups[child_name]) for group in groups.values(): if group.depth == 0 and group.name != 'all': all.add_child_group(group) return groups
def test_depth_update_dual_branches(self): alpha = Group('alpha') A = Group('A') alpha.add_child_group(A) B = Group('B') A.add_child_group(B) Z = Group('Z') alpha.add_child_group(Z) beta = Group('beta') B.add_child_group(beta) Z.add_child_group(beta) self.assertEqual(alpha.depth, 0) # apex self.assertEqual(beta.depth, 3) # alpha -> A -> B -> beta omega = Group('omega') omega.add_child_group(alpha) # verify that both paths are traversed to get the max depth value self.assertEqual(B.depth, 3) # omega -> alpha -> A -> B self.assertEqual(beta.depth, 4) # B -> beta
def _parse_base_groups(self): # FIXME: refactor ungrouped = Group(name='ungrouped') all = Group(name='all') all.add_child_group(ungrouped) self.groups = dict(all=all, ungrouped=ungrouped) active_group_name = 'ungrouped' for line in self.lines: if line.startswith("["): active_group_name = line.split(" #")[0].replace("[","").replace("]","").strip() if line.find(":vars") != -1 or line.find(":children") != -1: active_group_name = active_group_name.rsplit(":", 1)[0] if active_group_name not in self.groups: new_group = self.groups[active_group_name] = Group(name=active_group_name) all.add_child_group(new_group) active_group_name = None elif active_group_name not in self.groups: new_group = self.groups[active_group_name] = Group(name=active_group_name) all.add_child_group(new_group) elif line.startswith("#") or line.startswith(";") or line == '': pass elif active_group_name: tokens = shlex.split(line.split(" #")[0]) if len(tokens) == 0: continue hostname = tokens[0] port = C.DEFAULT_REMOTE_PORT # Two cases to check: # 0. A hostname that contains a range pesudo-code and a port # 1. A hostname that contains just a port if (hostname.find("[") != -1 and hostname.find("]") != -1 and hostname.find(":") != -1 and (hostname.rindex("]") < hostname.rindex(":")) or (hostname.find("]") == -1 and hostname.find(":") != -1)): tokens2 = hostname.rsplit(":", 1) hostname = tokens2[0] port = tokens2[1] hostnames = [] if detect_range(hostname): hostnames = expand_hostname_range(hostname) else: hostnames = [hostname] for hn in hostnames: host = None if hn in self.hosts: host = self.hosts[hn] else: host = Host(name=hn, port=port) self.hosts[hn] = host if len(tokens) > 1: for t in tokens[1:]: if t.startswith('#'): break try: (k,v) = t.split("=") except ValueError, e: raise errors.AnsibleError("Invalid ini entry: %s - %s" % (t, str(e))) try: host.set_variable(k,ast.literal_eval(v)) except: # most likely a string that literal_eval # doesn't like, so just set it host.set_variable(k,v) self.groups[active_group_name].add_host(host)
def _parse_base_groups(self): # FIXME: refactor ungrouped = Group(name='ungrouped') all = Group(name='all') all.add_child_group(ungrouped) self.groups = dict(all=all, ungrouped=ungrouped) active_group_name = 'ungrouped' for line in self.lines: line = line.split("#")[0].strip() if line.startswith("[") and line.endswith("]"): active_group_name = line.replace("[", "").replace("]", "") if line.find(":vars") != -1 or line.find(":children") != -1: active_group_name = active_group_name.rsplit(":", 1)[0] if active_group_name not in self.groups: new_group = self.groups[active_group_name] = Group( name=active_group_name) all.add_child_group(new_group) active_group_name = None elif active_group_name not in self.groups: new_group = self.groups[active_group_name] = Group( name=active_group_name) all.add_child_group(new_group) elif line.startswith(";") or line == '': pass elif active_group_name: tokens = shlex.split(line) if len(tokens) == 0: continue hostname = tokens[0] port = C.DEFAULT_REMOTE_PORT # Three cases to check: # 0. A hostname that contains a range pesudo-code and a port # 1. A hostname that contains just a port if hostname.count(":") > 1: # probably an IPv6 addresss, so check for the format # XXX:XXX::XXX.port, otherwise we'll just assume no # port is set if hostname.find(".") != -1: (hostname, port) = hostname.rsplit(".", 1) elif (hostname.find("[") != -1 and hostname.find("]") != -1 and hostname.find(":") != -1 and (hostname.rindex("]") < hostname.rindex(":")) or (hostname.find("]") == -1 and hostname.find(":") != -1)): (hostname, port) = hostname.rsplit(":", 1) hostnames = [] if detect_range(hostname): hostnames = expand_hostname_range(hostname) else: hostnames = [hostname] for hn in hostnames: host = None if hn in self.hosts: host = self.hosts[hn] else: host = Host(name=hn, port=port) self.hosts[hn] = host if len(tokens) > 1: for t in tokens[1:]: if t.startswith('#'): break try: (k, v) = t.split("=") except ValueError, e: raise errors.AnsibleError( "Invalid ini entry: %s - %s" % (t, str(e))) try: host.set_variable(k, ast.literal_eval(v)) except: # most likely a string that literal_eval # doesn't like, so just set it host.set_variable(k, v) self.groups[active_group_name].add_host(host)
def _parse_base_groups(self): # FIXME: refactor ungrouped = Group(name='ungrouped') all = Group(name='all') all.add_child_group(ungrouped) self.groups = dict(all=all, ungrouped=ungrouped) active_group_name = 'ungrouped' for line in self.lines: if line.startswith("["): active_group_name = line.split(" #")[0].replace( "[", "").replace("]", "").strip() if line.find(":vars") != -1 or line.find(":children") != -1: active_group_name = active_group_name.rsplit(":", 1)[0] if active_group_name not in self.groups: new_group = self.groups[active_group_name] = Group( name=active_group_name) all.add_child_group(new_group) active_group_name = None elif active_group_name not in self.groups: new_group = self.groups[active_group_name] = Group( name=active_group_name) all.add_child_group(new_group) elif line.startswith("#") or line.startswith(";") or line == '': pass elif active_group_name: tokens = shlex.split(line.split(" #")[0]) if len(tokens) == 0: continue hostname = tokens[0] port = C.DEFAULT_REMOTE_PORT # Two cases to check: # 0. A hostname that contains a range pesudo-code and a port # 1. A hostname that contains just a port if (hostname.find("[") != -1 and hostname.find("]") != -1 and hostname.find(":") != -1 and (hostname.rindex("]") < hostname.rindex(":")) or (hostname.find("]") == -1 and hostname.find(":") != -1)): tokens2 = hostname.rsplit(":", 1) hostname = tokens2[0] port = tokens2[1] hostnames = [] if detect_range(hostname): hostnames = expand_hostname_range(hostname) else: hostnames = [hostname] for hn in hostnames: host = None if hn in self.hosts: host = self.hosts[hn] else: host = Host(name=hn, port=port) self.hosts[hn] = host if len(tokens) > 1: for t in tokens[1:]: if t.startswith('#'): break try: (k, v) = t.split("=") except ValueError, e: raise errors.AnsibleError( "Invalid ini entry: %s - %s" % (t, str(e))) host.set_variable(k, v) self.groups[active_group_name].add_host(host)
def _parse_base_groups(self): # FIXME: refactor,貌似在后面的版本中该函数会被重构 # 这部分解析ini配置文件的代码却是写的很臃肿,急需要重构 # 基础group解析,除了已经定义组名的group以外,还有ungrouped表示未分组的组名,all表示所有组的总和 ungrouped = Group(name='ungrouped') all = Group(name='all') all.add_child_group(ungrouped) # 喜欢这种dict创建字典的方式,比用{}的方式好看多了 self.groups = dict(all=all, ungrouped=ungrouped) active_group_name = 'ungrouped' # 遍历self.lines,self.lines是文件内容, # ini文件的解析大多可以用python内置的conparser类,不过ansible的inventory所支持的语法比较复杂,这里作者自己处理了 for lineno in range(len(self.lines)): line = utils.before_comment(self.lines[lineno]).strip() # 如果某行以'['开头,以']'结尾则表名是一个section if line.startswith("[") and line.endswith("]"): # 将中括号replace掉获得组名 active_group_name = line.replace("[","").replace("]","") # 如果组名中有:vars 或:children,则进行二次处理,比如[southeast:vars],在通过冒号分割得到southeast if ":vars" in line or ":children" in line: active_group_name = active_group_name.rsplit(":", 1)[0] # rsplit(":", 1)表示右边开始以冒号为分隔符分割一次 # 如果组名未加到self.groups里面,则创建一个新的Group类,并添加到self.groups里面 # 如果组名已经存在与self.groups里面,跳过... if active_group_name not in self.groups: new_group = self.groups[active_group_name] = Group(name=active_group_name) # 在这种情况下将active_group_name设置为None,用来表示这不是一个包含真正host的group active_group_name = None # 这部分是组名中没有冒号的处理方式,和上面一样。 elif active_group_name not in self.groups: new_group = self.groups[active_group_name] = Group(name=active_group_name) elif line.startswith(";") or line == '': # 如果改行以分号开始或为空行则跳过。 pass elif active_group_name: # 这种情况表示当前行为非中括号打头的行,既包含真实host主机数据的行 # 在section中包含:vars/:children的段并不包含真实主机 # shlex模块实现了一个类来解析简单的类shell语法,可以用来编写领域特定的语言,或者解析加引号的字符串。 tokens = shlex.split(line) if len(tokens) == 0: continue hostname = tokens[0] # 获取主机名 port = C.DEFAULT_REMOTE_PORT # 使用默认的SSH端口号 # Three cases to check: # 0. A hostname that contains a range pesudo-code and a port,like badwol[a:f].example.com:5309 # 1. A hostname that contains just a port ,like badwolf.example.com:5309 # 对hostname需要进行以下的检测 if hostname.count(":") > 1: # IPV6格式的地址,端口号和hostname之间用"."表示。 # Possible an IPv6 address, or maybe a host line with multiple ranges # IPv6 with Port XXX:XXX::XXX.port # FQDN foo.example.com if hostname.count(".") == 1: (hostname, port) = hostname.rsplit(".", 1) elif ("[" in hostname and "]" in hostname and ":" in hostname and (hostname.rindex("]") < hostname.rindex(":")) or ("]" not in hostname and ":" in hostname)): # 如果冒号在中括号外面,表示这个冒号后面是端口号,因此通过通过rsplit按照冒号分割一次获取端口号和hostname (hostname, port) = hostname.rsplit(":", 1) hostnames = [] # 检测hostname是否是表示一个范围的hosts,如果是则将其扩展成一组host列表,否则加入空列表 if detect_range(hostname): hostnames = expand_hostname_range(hostname) else: hostnames = [hostname] # 遍历hostnames列表 for hn in hostnames: host = None if hn in self.hosts: host = self.hosts[hn] else: # 如果host不在self.hosts列表中,则创建一个Host基类 host = Host(name=hn, port=port) self.hosts[hn] = host # len(tokens) > 1表示该行拥有变量,如:jumper ansible_ssh_port=5555 ansible_ssh_host=192.168.1.50 if len(tokens) > 1: for t in tokens[1:]: if t.startswith('#'): # 如果是注释则退出,在ini文件中仍然可以使用#作为注释标识。 break try: (k,v) = t.split("=", 1) # kv变量解析 except ValueError, e: raise errors.AnsibleError("%s:%s: Invalid ini entry: %s - %s" % (self.filename, lineno + 1, t, str(e))) host.set_variable(k, self._parse_value(v)) # 将该行解析的变量设置到该host下 self.groups[active_group_name].add_host(host) # 将该host加入到对应的group中。
def _parse_base_groups(self): # FIXME: refactor ungrouped = Group(name='ungrouped') all = Group(name='all') all.add_child_group(ungrouped) self.groups = dict(all=all, ungrouped=ungrouped) active_group_name = 'ungrouped' for line in self.lines: line = utils.before_comment(line).strip() if line.startswith("[") and line.endswith("]"): active_group_name = line.replace("[","").replace("]","") if line.find(":vars") != -1 or line.find(":children") != -1: active_group_name = active_group_name.rsplit(":", 1)[0] if active_group_name not in self.groups: new_group = self.groups[active_group_name] = Group(name=active_group_name) all.add_child_group(new_group) active_group_name = None elif active_group_name not in self.groups: new_group = self.groups[active_group_name] = Group(name=active_group_name) all.add_child_group(new_group) elif line.startswith(";") or line == '': pass elif active_group_name: tokens = shlex.split(line) if len(tokens) == 0: continue hostname = tokens[0] port = C.DEFAULT_REMOTE_PORT # Three cases to check: # 0. A hostname that contains a range pesudo-code and a port # 1. A hostname that contains just a port if hostname.count(":") > 1: # Possible an IPv6 address, or maybe a host line with multiple ranges # IPv6 with Port XXX:XXX::XXX.port # FQDN foo.example.com if hostname.count(".") == 1: (hostname, port) = hostname.rsplit(".", 1) elif (hostname.find("[") != -1 and hostname.find("]") != -1 and hostname.find(":") != -1 and (hostname.rindex("]") < hostname.rindex(":")) or (hostname.find("]") == -1 and hostname.find(":") != -1)): (hostname, port) = hostname.rsplit(":", 1) hostnames = [] if detect_range(hostname): hostnames = expand_hostname_range(hostname) else: hostnames = [hostname] for hn in hostnames: host = None if hn in self.hosts: host = self.hosts[hn] else: host = Host(name=hn, port=port) self.hosts[hn] = host if len(tokens) > 1: for t in tokens[1:]: if t.startswith('#'): break try: (k,v) = t.split("=", 1) except ValueError, e: raise errors.AnsibleError("Invalid ini entry: %s - %s" % (t, str(e))) # If there is a hash in the value don't pass it through to ast at ast will split at the hash. if "#" in v: host.set_variable(k, v) else: try: host.set_variable(k,ast.literal_eval(v)) # Using explicit exceptions. # Likely a string that literal_eval does not like. We wil then just set it. except ValueError: # For some reason this was thought to be malformed. host.set_variable(k, v) except SyntaxError: # Is this a hash with an equals at the end? host.set_variable(k, v) self.groups[active_group_name].add_host(host)
def _parse_base_groups(self): # FIXME: refactor ungrouped = Group(name='ungrouped') all = Group(name='all') all.add_child_group(ungrouped) self.groups = dict(all=all, ungrouped=ungrouped) active_group_name = 'ungrouped' for line in self.lines: line = utils.before_comment(line).strip() if line.startswith("[") and line.endswith("]"): active_group_name = line.replace("[","").replace("]","") if ":vars" in line or ":children" in line: active_group_name = active_group_name.rsplit(":", 1)[0] if active_group_name not in self.groups: new_group = self.groups[active_group_name] = Group(name=active_group_name) all.add_child_group(new_group) active_group_name = None elif active_group_name not in self.groups: new_group = self.groups[active_group_name] = Group(name=active_group_name) all.add_child_group(new_group) elif line.startswith(";") or line == '': pass elif active_group_name: tokens = shlex.split(line) if len(tokens) == 0: continue hostname = tokens[0] port = C.DEFAULT_REMOTE_PORT # Three cases to check: # 0. A hostname that contains a range pesudo-code and a port # 1. A hostname that contains just a port if hostname.count(":") > 1: # Possible an IPv6 address, or maybe a host line with multiple ranges # IPv6 with Port XXX:XXX::XXX.port # FQDN foo.example.com if hostname.count(".") == 1: (hostname, port) = hostname.rsplit(".", 1) elif ("[" in hostname and "]" in hostname and ":" in hostname and (hostname.rindex("]") < hostname.rindex(":")) or ("]" not in hostname and ":" in hostname)): (hostname, port) = hostname.rsplit(":", 1) hostnames = [] if detect_range(hostname): hostnames = expand_hostname_range(hostname) else: hostnames = [hostname] for hn in hostnames: host = None if hn in self.hosts: host = self.hosts[hn] else: host = Host(name=hn, port=port) self.hosts[hn] = host if len(tokens) > 1: for t in tokens[1:]: if t.startswith('#'): break try: (k,v) = t.split("=", 1) except ValueError, e: raise errors.AnsibleError("Invalid ini entry: %s - %s" % (t, str(e))) # If there is a hash in the value don't pass it through to ast at ast will split at the hash. if "#" in v: host.set_variable(k, v) else: try: host.set_variable(k,ast.literal_eval(v)) # Using explicit exceptions. # Likely a string that literal_eval does not like. We wil then just set it. except ValueError: # For some reason this was thought to be malformed. host.set_variable(k, v) except SyntaxError: # Is this a hash with an equals at the end? host.set_variable(k, v) self.groups[active_group_name].add_host(host)
def parse_inventory(self, host_list): if isinstance(host_list, string_types): if "," in host_list: host_list = host_list.split(",") host_list = [ h for h in host_list if h and h.strip() ] self.parser = None # Always create the 'all' and 'ungrouped' groups, even if host_list is # empty: in this case we will subsequently add the implicit 'localhost' to it. ungrouped = Group('ungrouped') all = Group('all') all.add_child_group(ungrouped) base_groups = frozenset([all, ungrouped]) self.groups = dict(all=all, ungrouped=ungrouped) if host_list is None: pass elif isinstance(host_list, list): for h in host_list: try: (host, port) = parse_address(h, allow_ranges=False) except AnsibleError as e: display.vvv("Unable to parse address from hostname, leaving unchanged: %s" % to_text(e)) host = h port = None new_host = Host(host, port) if h in C.LOCALHOST: # set default localhost from inventory to avoid creating an implicit one. Last localhost defined 'wins'. if self.localhost is not None: display.warning("A duplicate localhost-like entry was found (%s). First found localhost was %s" % (h, self.localhost.name)) display.vvvv("Set default localhost to %s" % h) self.localhost = new_host all.add_host(new_host) elif self._loader.path_exists(host_list): # TODO: switch this to a plugin loader and a 'condition' per plugin on which it should be tried, restoring 'inventory pllugins' if self.is_directory(host_list): # Ensure basedir is inside the directory host_list = os.path.join(self.host_list, "") self.parser = InventoryDirectory(loader=self._loader, groups=self.groups, filename=host_list) else: self.parser = get_file_parser(host_list, self.groups, self._loader) vars_loader.add_directory(self._basedir, with_subdir=True) if not self.parser: # should never happen, but JIC raise AnsibleError("Unable to parse %s as an inventory source" % host_list) else: display.warning("Host file not found: %s" % to_text(host_list)) self._vars_plugins = [ x for x in vars_loader.all(self) ] ### POST PROCESS groups and hosts after specific parser was invoked group_names = set() # set group vars from group_vars/ files and vars plugins for g in self.groups: group = self.groups[g] group.vars = combine_vars(group.vars, self.get_group_variables(group.name)) self.get_group_vars(group) group_names.add(group.name) host_names = set() # get host vars from host_vars/ files and vars plugins for host in self.get_hosts(ignore_limits=True, ignore_restrictions=True): host.vars = combine_vars(host.vars, self.get_host_variables(host.name)) self.get_host_vars(host) host_names.add(host.name) mygroups = host.get_groups() # ensure hosts are always in 'all' if all not in mygroups: all.add_host(host) if ungrouped in mygroups: # clear ungrouped of any incorrectly stored by parser if set(mygroups).difference(base_groups): host.remove_group(ungrouped) else: # add ungrouped hosts to ungrouped length = len(mygroups) if length == 0 or (length == 1 and all in mygroups): ungrouped.add_host(host) # warn if overloading identifier as both group and host for conflict in group_names.intersection(host_names): display.warning("Found both group and host with same name: %s" % conflict)
def _parse_base_groups(self): # FIXME: refactor ungrouped = Group(name='ungrouped') all = Group(name='all') all.add_child_group(ungrouped) self.groups = dict(all=all, ungrouped=ungrouped) active_group_name = 'ungrouped' for line in self.lines: line = utils.before_comment(line).strip() if line.startswith("[") and line.endswith("]"): active_group_name = line.replace("[", "").replace("]", "") if ":vars" in line or ":children" in line: active_group_name = active_group_name.rsplit(":", 1)[0] if active_group_name not in self.groups: new_group = self.groups[active_group_name] = Group( name=active_group_name) active_group_name = None elif active_group_name not in self.groups: new_group = self.groups[active_group_name] = Group( name=active_group_name) elif line.startswith(";") or line == '': pass elif active_group_name: tokens = shlex.split(line) if len(tokens) == 0: continue hostname = tokens[0] port = C.DEFAULT_REMOTE_PORT # Three cases to check: # 0. A hostname that contains a range pesudo-code and a port # 1. A hostname that contains just a port if hostname.count(":") > 1: # Possible an IPv6 address, or maybe a host line with multiple ranges # IPv6 with Port XXX:XXX::XXX.port # FQDN foo.example.com if hostname.count(".") == 1: (hostname, port) = hostname.rsplit(".", 1) elif ("[" in hostname and "]" in hostname and ":" in hostname and (hostname.rindex("]") < hostname.rindex(":")) or ("]" not in hostname and ":" in hostname)): (hostname, port) = hostname.rsplit(":", 1) hostnames = [] if detect_range(hostname): hostnames = expand_hostname_range(hostname) else: hostnames = [hostname] for hn in hostnames: host = None if hn in self.hosts: host = self.hosts[hn] else: host = Host(name=hn, port=port) self.hosts[hn] = host if len(tokens) > 1: for t in tokens[1:]: if t.startswith('#'): break try: (k, v) = t.split("=", 1) except ValueError, e: raise errors.AnsibleError( "Invalid ini entry: %s - %s" % (t, str(e))) host.set_variable(k, self._parse_value(v)) self.groups[active_group_name].add_host(host)
def _parse(self, err): all_hosts = {} # not passing from_remote because data from CMDB is trusted self.raw = utils.parse_json(self.data) # 还是使用self.data来解析标准输出,需要self.data为json格式数据 self.raw = json_dict_bytes_to_unicode(self.raw) # 将self.raw中的kv都转换成unicode格式。 all = Group('all') # 设置Group("all") groups = dict(all=all) # 初始化groups字典 group = None if 'failed' in self.raw: # 如果self.raw中有failed字段,则报错。不过在上面parser_json的时候no_exception是false,不会出现failed情况 sys.stderr.write(err + "\n") raise errors.AnsibleError("failed to parse executable inventory script results: %s" % self.raw) for (group_name, data) in self.raw.items(): # 1.3 以上版本使用--list的时返回结果中会包含_meta这样的key,该key的value中会有一个hostvars变量,该变量包含每个host的主机变量 # 1.2及以下版本仍然需要使用--host命令为每一个host返回主机变量 # in Ansible 1.3 and later, a "_meta" subelement may contain # a variable "hostvars" which contains a hash for each host # if this "hostvars" exists at all then do not call --host for each # host. This is for efficiency and scripts should still return data # if called with --host for backwards compat with 1.2 and earlier. """ { "databases" : { "hosts" : [ "host1.example.com", "host2.example.com" ], "vars" : { "a" : true } }, "webservers" : [ "host2.example.com", "host3.example.com" ], "atlanta" : { "hosts" : [ "host1.example.com", "host4.example.com", "host5.example.com" ], "vars" : { "b" : false }, "children": [ "marietta", "5points" ] }, "marietta" : [ "host6.example.com" ], "5points" : [ "host7.example.com" ] } { # results of inventory script as above go here # ... "_meta" : { "hostvars" : { "moocow.example.com" : { "asdf" : 1234 }, "llama.example.com" : { "asdf" : 5678 }, } } { "moocow.example.com" : { "asdf" : 1234 }, "llama.example.com" : { "asdf" : 5678 } } """ if group_name == '_meta': # 如果key为_meta,则该value为meta数据。 if 'hostvars' in data: self.host_vars_from_top = data['hostvars'] # 如果meta数据中包含hostvars,则缓存到self.host_vars_from_top中。 continue # 跳过之后的处理 if group_name != all.name: # group_name不是all则创建新的Group对象,并加入groups字典。 group = groups[group_name] = Group(group_name) # 如果data中出现无group的hostname,则会出现以hostname为名称的组 else: group = all host = None if not isinstance(data, dict): # 如果data不是字典类型,则表示data为主机列表 data = {'hosts': data} # is not those subkeys, then simplified syntax, host with vars elif not any(k in data for k in ('hosts','vars','children')): # any函数表示可迭代对象中任何一个为True,则为True.否则为False # 如果data对象是字典类型,但key中不存在hosts、vars、children时,既该行数据为主机变量数据时,group_name为主机名 data = {'hosts': [group_name], 'vars': data} # 上面两步为了统一data的格式,不带变量的data,格式为{'hosts': [host1,host2...] # 带变量的data,格式为: {'hosts': [group_name], 'vars': data } if 'hosts' in data: if not isinstance(data['hosts'], list): # 主机列表必须是list对象,上面的'hosts': [group_name] 也是为校验统一格式 raise errors.AnsibleError("You defined a group \"%s\" with bad " "data for the host list:\n %s" % (group_name, data)) for hostname in data['hosts']: # 遍历主机名 if not hostname in all_hosts: all_hosts[hostname] = Host(hostname) # 如果主机对象不存在, 则创建并添加到all_hosts列表中,去重 host = all_hosts[hostname] # 将Host对象赋值给host变量 group.add_host(host) # 将Host对象添加到该group中 if 'vars' in data: # 如果是带有vars的data if not isinstance(data['vars'], dict): raise errors.AnsibleError("You defined a group \"%s\" with bad " "data for variables:\n %s" % (group_name, data)) for k, v in data['vars'].iteritems(): # 遍历所有的变量 if group.name == all.name: # 如果当前组名为“all",则将变量加到all group中,否则加到当前group中。 all.set_variable(k, v) else: group.set_variable(k, v) # Separate loop to ensure all groups are defined for (group_name, data) in self.raw.items(): if group_name == '_meta': continue if isinstance(data, dict) and 'children' in data: # 如果data中包含子组,遍历子组并将子组添加到父组中 for child_name in data['children']: if child_name in groups: groups[group_name].add_child_group(groups[child_name]) for group in groups.values(): if group.depth == 0 and group.name != 'all': # 如果该组深度为0,且名称不是"all",则加入all组的子组列表中。 all.add_child_group(group) return groups