docs: def build_inventory(setup):
      '''Builds an inventory for use as part of an
      `dynamic Ansible inventory <http://docs.ansible.com/ansible/intro_dynamic_inventory.html>`_
      according to the
      `script conventions <http://docs.ansible.com/ansible/developing_inventory.html#script-conventions>`_::
  
          {
              "_meta" : {
                  "hostvars" : {
                      "host1": {},
                      "host2": {},
                      "host3": {},
                      ...
              },
              "all": {
                  "vars": {}
              },
              "group1": {
                  "hosts": ["host1", "host2"],
                  "vars": {}
              },
              "group2": {
                  "hosts": ["host3"],
                  "vars": {}
              },
              ...
          }
  
  
      Parameters
      ----------
      setup: tmdeploy.config.Setup
          setup configuration
  
      Returns
      -------
      dict
          mapping of groups to hosts
      '''
      inventory = dict()
      inventory['all'] = dict()
      inventory['all']['vars'] = {
          'provider': setup.cloud.provider,
          'region': setup.cloud.region,
          'key_name': setup.cloud.key_name,
          'key_file': os.path.expandvars(
              os.path.expanduser(
                  setup.cloud.key_file_public
              )
          ),
          'network': setup.cloud.network,
          'subnetwork': setup.cloud.subnetwork,
          'ip_range': setup.cloud.ip_range,
          'proxy_env': {
              'http_proxy': setup.cloud.proxy,
              'https_proxy': setup.cloud.proxy
          }
      }
      inventory['_meta'] = dict()
      inventory['_meta']['hostvars'] = dict()
  
      if not isinstance(setup, Setup):
          raise TypeError(
              'Argument "setup" must have type {0}.'.format(
                  '.'.join([Setup.__module__, Setup.__name__])
              )
          )
      for cluster in setup.architecture.clusters:
          logger.info('configure cluster "%s"', cluster.name)
          for node_type in cluster.node_types:
              logger.info('configure node type "%s"', node_type.name)
              for i in range(node_type.count):
                  host_name = HOSTNAME_FORMAT.format(
                      name=setup.architecture.name, cluster=cluster.name,
                      node_type=node_type.name, index=i+1
                  )
                  host_vars = dict()
                  for k, v in node_type.instance.to_dict().items():
                      if k == 'tags':
                          # Every server is part of the "compute-storage"
                          # security group, which is important for servers to be
                          # able to connect to each other when part of a cluster.
                          security_groups = 'compute-storage'
                          if 'web' in v:
                              host_vars['assign_public_ip'] = 'yes'
                              security_groups = ','.join([
                                  security_groups, 'web'
                              ])
                          else:
                              host_vars['assign_public_ip'] = 'no'
                          host_vars['security_groups'] = security_groups
                      if isinstance(v, list):
                          v = ','.join(v)
                      host_vars[k] = v
                  inventory['_meta']['hostvars'][host_name] = host_vars
                  for group in node_type.groups:
                      logger.info('add group "%s"', group.name)
                      if group.name not in inventory:
                          inventory[group.name] = {'hosts': list()}
                      inventory[group.name]['hosts'].append(host_name)
                      inventory[group.name]['vars'] = dict()
                      if group.vars is not None:
                          inventory[group.name]['vars'].update(group.vars)
                      if node_type.vars is not None:
                          inventory[group.name]['vars'].update(node_type.vars)
  
      return inventory