Exemple #1
0
    def _parse(self, path, lines):
        '''
        Populates self.groups from the given array of lines. Raises an error on
        any parse failure.
        '''

        self._compile_patterns()

        # We behave as though the first line of the inventory is '[ungrouped]',
        # and begin to look for host definitions. We make a single pass through
        # each line of the inventory, building up self.groups and adding hosts,
        # subgroups, and setting variables as we go.

        pending_declarations = {}
        groupname = 'ungrouped'
        state = 'hosts'
        self.lineno = 0
        for line in lines:
            self.lineno += 1

            line = line.strip()
            # Skip empty lines and comments
            if not line or line[0] in self._COMMENT_MARKERS:
                continue

            # Is this a [section] header? That tells us what group we're parsing
            # definitions for, and what kind of definitions to expect.

            m = self.patterns['section'].match(line)
            if m:
                (groupname, state) = m.groups()

                groupname = to_safe_group_name(groupname)

                state = state or 'hosts'
                if state not in ['hosts', 'children', 'vars']:
                    title = ":".join(m.groups())
                    self._raise_error("Section [%s] has unknown type: %s" %
                                      (title, state))

                # If we haven't seen this group before, we add a new Group.
                if groupname not in self.inventory.groups:
                    # Either [groupname] or [groupname:children] is sufficient to declare a group,
                    # but [groupname:vars] is allowed only if the # group is declared elsewhere.
                    # We add the group anyway, but make a note in pending_declarations to check at the end.
                    #
                    # It's possible that a group is previously pending due to being defined as a child
                    # group, in that case we simply pass so that the logic below to process pending
                    # declarations will take the appropriate action for a pending child group instead of
                    # incorrectly handling it as a var state pending declaration
                    if state == 'vars' and groupname not in pending_declarations:
                        pending_declarations[groupname] = dict(
                            line=self.lineno, state=state, name=groupname)

                    self.inventory.add_group(groupname)

                # When we see a declaration that we've been waiting for, we process and delete.
                if groupname in pending_declarations and state != 'vars':
                    if pending_declarations[groupname]['state'] == 'children':
                        self._add_pending_children(groupname,
                                                   pending_declarations)
                    elif pending_declarations[groupname]['state'] == 'vars':
                        del pending_declarations[groupname]

                continue
            elif line.startswith('[') and line.endswith(']'):
                self._raise_error(
                    "Invalid section entry: '%s'. Please make sure that there are no spaces"
                    % line +
                    "in the section entry, and that there are no other invalid characters"
                )

            # It's not a section, so the current state tells us what kind of
            # definition it must be. The individual parsers will raise an
            # error if we feed them something they can't digest.

            # [groupname] contains host definitions that must be added to
            # the current group.
            if state == 'hosts':
                hosts, port, variables = self._parse_host_definition(line)
                self._populate_host_vars(hosts, variables, groupname, port)

            # [groupname:vars] contains variable definitions that must be
            # applied to the current group.
            elif state == 'vars':
                (k, v) = self._parse_variable_definition(line)
                self.inventory.set_variable(groupname, k, v)

            # [groupname:children] contains subgroup names that must be
            # added as children of the current group. The subgroup names
            # must themselves be declared as groups, but as before, they
            # may only be declared later.
            elif state == 'children':
                child = self._parse_group_name(line)
                if child not in self.inventory.groups:
                    if child not in pending_declarations:
                        pending_declarations[child] = dict(line=self.lineno,
                                                           state=state,
                                                           name=child,
                                                           parents=[groupname])
                    else:
                        pending_declarations[child]['parents'].append(
                            groupname)
                else:
                    self.inventory.add_child(groupname, child)
            else:
                # This can happen only if the state checker accepts a state that isn't handled above.
                self._raise_error("Entered unhandled state: %s" % (state))

        # Any entries in pending_declarations not removed by a group declaration above mean that there was an unresolved reference.
        # We report only the first such error here.
        for g in pending_declarations:
            decl = pending_declarations[g]
            if decl['state'] == 'vars':
                raise AnsibleError(
                    "%s:%d: Section [%s:vars] not valid for undefined group: %s"
                    % (path, decl['line'], decl['name'], decl['name']))
            elif decl['state'] == 'children':
                raise AnsibleError(
                    "%s:%d: Section [%s:children] includes undefined group: %s"
                    %
                    (path, decl['line'], decl['parents'].pop(), decl['name']))
    def parse(self, inventory, loader, path, cache=True):
        super(InventoryModule, self).parse(inventory, loader, path)

        self._read_config_data(path)

        self._token = self.get_option('api_token')
        if not self._token:
            raise AnsibleError('Could not find an API token. Set the '
                               'CLOUDSCALE_API_TOKEN environment variable.')

        inventory_hostname = self.get_option('inventory_hostname')
        if inventory_hostname not in ('name', 'uuid'):
            raise AnsibleError(
                'Invalid value for option inventory_hostname: %s' %
                inventory_hostname)

        ansible_host = self.get_option('ansible_host')
        if ansible_host not in iface_type_map:
            raise AnsibleError('Invalid value for option ansible_host: %s' %
                               ansible_host)

        # Merge servers with the same name
        firstpass = defaultdict(list)
        for server in self._get_server_list():
            firstpass[server['name']].append(server)

        # Add servers to inventory
        for name, servers in firstpass.items():
            if len(servers) == 1 and inventory_hostname == 'name':
                self.inventory.add_host(name)
                servers[0]['inventory_hostname'] = name
            else:
                # Two servers with the same name exist, create a group
                # with this name and add the servers by UUID
                group_name = to_safe_group_name(name)
                if group_name not in self.inventory.groups:
                    self.inventory.add_group(group_name)
                for server in servers:
                    self.inventory.add_host(server['uuid'], group_name)
                    server['inventory_hostname'] = server['uuid']

            # Set variables
            iface_type, iface_version = iface_type_map[ansible_host]
            for server in servers:
                hostname = server.pop('inventory_hostname')
                if ansible_host != 'none':
                    addresses = [
                        address['address']
                        for interface in server['interfaces']
                        for address in interface['addresses']
                        if interface['type'] == iface_type
                        and address['version'] == iface_version
                    ]

                    if len(addresses) > 0:
                        self.inventory.set_variable(
                            hostname,
                            'ansible_host',
                            addresses[0],
                        )
                self.inventory.set_variable(
                    hostname,
                    'cloudscale',
                    server,
                )

                variables = self.inventory.hosts[hostname].get_vars()
                # Set composed variables
                self._set_composite_vars(
                    self.get_option('compose'),
                    variables,
                    hostname,
                    self.get_option('strict'),
                )

                # Add host to composed groups
                self._add_host_to_composed_groups(
                    self.get_option('groups'),
                    variables,
                    hostname,
                    self.get_option('strict'),
                )

                # Add host to keyed groups
                self._add_host_to_keyed_groups(
                    self.get_option('keyed_groups'),
                    variables,
                    hostname,
                    self.get_option('strict'),
                )
Exemple #3
0
def safe_group_name(name):
    return to_safe_group_name(name)