docs: def setup_cluster(self, cluster, extra_args=tuple()):
          """
          Configure the cluster by running an Ansible playbook.
  
          The ElastiCluster configuration attribute `<kind>_groups`
          determines, for each node kind, what Ansible groups nodes of
          that kind are assigned to.
  
          :param cluster: cluster to configure
          :type cluster: :py:class:`elasticluster.cluster.Cluster`
  
          :param list extra_args:
            List of additional command-line arguments
            that are appended to each invocation of the setup program.
  
          :return: ``True`` on success, ``False`` otherwise. Please note, if nothing
                   has to be configured, then ``True`` is returned.
  
          :raises: `ConfigurationError` if the playbook can not be found
                   or is corrupt.
          """
          inventory_path = self._build_inventory(cluster)
          if inventory_path is None:
              # No inventory file has been created, maybe an
              # invalid class has been specified in config file? Or none?
              # assume it is fine.
              elasticluster.log.info("No setup required for this cluster.")
              return True
          assert os.path.exists(inventory_path), (
                  "inventory file `{inventory_path}` does not exist"
                  .format(inventory_path=inventory_path))
  
          # build list of directories to search for roles/include files
          ansible_roles_dirs = [
              # include Ansible default first ...
              '/etc/ansible/roles',
          ]
          for root_path in [
                  # ... then ElastiCluster's built-in defaults
                  resource_filename('elasticluster', 'share/playbooks'),
                  # ... then wherever the playbook is
                  os.path.dirname(self._playbook_path),
          ]:
              for path in [
                      root_path,
                      os.path.join(root_path, 'roles'),
              ]:
                  if path not in ansible_roles_dirs and os.path.exists(path):
                      ansible_roles_dirs.append(path)
  
  
          # Use env vars to configure Ansible;
          # see all values in https://github.com/ansible/ansible/blob/devel/lib/ansible/constants.py
          #
          # Ansible does not merge keys in configuration files: rather
          # it uses the first configuration file found.  However,
          # environment variables can be used to selectively override
          # parts of the config; according to [1]: "they are mostly
          # considered to be a legacy system as compared to the config
          # file, but are equally valid."
          #
          # [1]: http://docs.ansible.com/ansible/intro_configuration.html#environmental-configuration
          #
          # Provide default values for important configuration variables...
          ansible_env = {
              'ANSIBLE_FORKS':             '10',
              'ANSIBLE_HOST_KEY_CHECKING': 'no',
              'ANSIBLE_PRIVATE_KEY_FILE':  cluster.user_key_private,
              'ANSIBLE_ROLES_PATH':        ':'.join(reversed(ansible_roles_dirs)),
              'ANSIBLE_SSH_PIPELINING':    'yes',
              'ANSIBLE_TIMEOUT':           '120',
          }
          # ...override them with key/values set in the config file(s)
          for k, v in self.extra_conf.items():
              if k.startswith('ansible_'):
                  ansible_env[k.upper()] = str(v)
          # ...finally allow the environment have the final word
          ansible_env.update(os.environ)
          if __debug__:
              elasticluster.log.debug(
                  "Calling `ansible-playbook` with the following environment:")
              for var, value in sorted(ansible_env.items()):
                  elasticluster.log.debug("- %s=%r", var, value)
  
          elasticluster.log.debug("Using playbook file %s.", self._playbook_path)
  
          # build `ansible-playbook` command-line
          cmd = shlex.split(self.extra_conf.get('ansible_command', 'ansible-playbook'))
          cmd += [
              os.path.realpath(self._playbook_path),
              ('--inventory=' + inventory_path),
          ] + list(extra_args)
  
          if self._sudo:
              cmd.extend([
                  # force all plays to use `sudo` (even if not marked as such)
                  '--become',
                  # desired sudo-to user
                  ('--become-user=' + self._sudo_user),
              ])
  
          # determine Ansible verbosity as a function of ElastiCluster's
          # log level (we cannot read `ElastiCluster().params.verbose`
          # here, still we can access the log configuration since it's
          # global).
          verbosity = (logging.WARNING - elasticluster.log.getEffectiveLevel()) / 10
          if verbosity > 0:
              cmd.append('-' + ('v' * verbosity))  # e.g., `-vv`
  
          # append any additional arguments provided by users
          ansible_extra_args = self.extra_conf.get('ansible_extra_args', None)
          if ansible_extra_args:
              cmd += shlex.split(ansible_extra_args)
  
          cmdline = ' '.join(cmd)
          elasticluster.log.debug("Running Ansible command `%s` ...", cmdline)
  
          rc = call(cmd, env=ansible_env, bufsize=1, close_fds=True)
          if rc == 0:
              elasticluster.log.info("Cluster correctly configured.")
              return True
          else:
              elasticluster.log.error(
                  "Command `%s` failed with exit code %d.", cmdline, rc)
              elasticluster.log.error(
                  "Check the output lines above for additional information on this error.")
              elasticluster.log.error(
                  "The cluster has likely *not* been configured correctly."
                  " You may need to re-run `elasticluster setup` or fix the playbooks.")
              return False