text: # -*- coding: utf-8 -*-
  # Copyright (C) 2016 Guido Günther <agx@sigxcpu.org>, Daniel Lobato Garcia <dlobatog@redhat.com>
  # Copyright (c) 2018 Ansible Project
  # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
  from __future__ import (absolute_import, division, print_function)
  __metaclass__ = type
  
  DOCUMENTATION = '''
      name: foreman
      plugin_type: inventory
      short_description: foreman inventory source
      version_added: "2.6"
      requirements:
          - requests >= 1.1
      description:
          - Get inventory hosts from the foreman service.
          - "Uses a configuration file as an inventory source, it must end in ``.foreman.yml`` or ``.foreman.yaml`` and has a ``plugin: foreman`` entry."
      extends_documentation_fragment:
          - inventory_cache
      options:
        plugin:
          description: the name of this plugin, it should always be set to 'foreman' for this plugin to recognize it as it's own.
          required: True
          choices: ['foreman']
        url:
          description: url to foreman
          default: 'http://localhost:3000'
          env:
              - name: FOREMAN_SERVER
                version_added: "2.8"
        user:
          description: foreman authentication user
          required: True
          env:
              - name: FOREMAN_USER
                version_added: "2.8"
        password:
          description: foreman authentication password
          required: True
          env:
              - name: FOREMAN_PASSWORD
                version_added: "2.8"
        validate_certs:
          description: verify SSL certificate if using https
          type: boolean
          default: False
        group_prefix:
          description: prefix to apply to foreman groups
          default: foreman_
        vars_prefix:
          description: prefix to apply to host variables, does not include facts nor params
          default: foreman_
        want_facts:
          description: Toggle, if True the plugin will retrieve host facts from the server
          type: boolean
          default: False
        want_params:
          description: Toggle, if true the inventory will retrieve 'all_parameters' information as host vars
          type: boolean
          default: False
  '''
  
  EXAMPLES = '''
  # my.foreman.yml
  plugin: foreman
  url: http://localhost:2222
  user: ansible-tester
  password: secure
  validate_certs: False
  '''
  
  from distutils.version import LooseVersion
  
  from ansible.errors import AnsibleError
  from ansible.module_utils._text import to_bytes, to_native
  from ansible.module_utils.common._collections_compat import MutableMapping
  from ansible.plugins.inventory import BaseInventoryPlugin, Cacheable, to_safe_group_name
  
  # 3rd party imports
  try:
      import requests
      if LooseVersion(requests.__version__) < LooseVersion('1.1.0'):
          raise ImportError
  except ImportError:
      raise AnsibleError('This script requires python-requests 1.1 as a minimum version')
  
  from requests.auth import HTTPBasicAuth
  
  
  class InventoryModule(BaseInventoryPlugin, Cacheable):
      ''' Host inventory parser for ansible using foreman as source. '''
  
      NAME = 'foreman'
  
      def __init__(self):
  
          super(InventoryModule, self).__init__()
  
          # from config
          self.foreman_url = None
  
          self.session = None
          self.cache_key = None
          self.use_cache = None
  
      def verify_file(self, path):
  
          valid = False
          if super(InventoryModule, self).verify_file(path):
              if path.endswith(('foreman.yaml', 'foreman.yml')):
                  valid = True
              else:
                  self.display.vvv('Skipping due to inventory source not ending in "foreman.yaml" nor "foreman.yml"')
          return valid
  
      def _get_session(self):
          if not self.session:
              self.session = requests.session()
              self.session.auth = HTTPBasicAuth(self.get_option('user'), to_bytes(self.get_option('password')))
              self.session.verify = self.get_option('validate_certs')
          return self.session
  
      def _get_json(self, url, ignore_errors=None):
  
          if not self.use_cache or url not in self._cache.get(self.cache_key, {}):
  
              if self.cache_key not in self._cache:
                  self._cache[self.cache_key] = {url: ''}
  
              results = []
              s = self._get_session()
              params = {'page': 1, 'per_page': 250}
              while True:
                  ret = s.get(url, params=params)
                  if ignore_errors and ret.status_code in ignore_errors:
                      break
                  ret.raise_for_status()
                  json = ret.json()
  
                  # process results
                  # FIXME: This assumes 'return type' matches a specific query,
                  #        it will break if we expand the queries and they dont have different types
                  if 'results' not in json:
                      # /hosts/:id dos not have a 'results' key
                      results = json
                      break
                  elif isinstance(json['results'], MutableMapping):
                      # /facts are returned as dict in 'results'
                      results = json['results']
                      break
                  else:
                      # /hosts 's 'results' is a list of all hosts, returned is paginated
                      results = results + json['results']
  
                      # check for end of paging
                      if len(results) >= json['subtotal']:
                          break
                      if len(json['results']) == 0:
                          self.display.warning("Did not make any progress during loop. expected %d got %d" % (json['subtotal'], len(results)))
                          break
  
                      # get next page
                      params['page'] += 1
  
              # Set the cache if it is enabled or if the cache was refreshed
              if self.use_cache or self.get_option('cache'):
                  self._cache[self.cache_key][url] = results
  
          return self._cache[self.cache_key][url]
  
      def _get_hosts(self):
          return self._get_json("%s/api/v2/hosts" % self.foreman_url)
  
      def _get_all_params_by_id(self, hid):
          url = "%s/api/v2/hosts/%s" % (self.foreman_url, hid)
          ret = self._get_json(url, [404])
          if not ret or not isinstance(ret, MutableMapping) or not ret.get('all_parameters', False):
              return {}
          return ret.get('all_parameters')
  
      def _get_facts_by_id(self, hid):
          url = "%s/api/v2/hosts/%s/facts" % (self.foreman_url, hid)
          return self._get_json(url)
  
      def _get_facts(self, host):
          """Fetch all host facts of the host"""
  
          ret = self._get_facts_by_id(host['id'])
          if len(ret.values()) == 0:
              facts = {}
          elif len(ret.values()) == 1:
              facts = list(ret.values())[0]
          else:
              raise ValueError("More than one set of facts returned for '%s'" % host)
          return facts
  
      def _populate(self):
  
          for host in self._get_hosts():
  
              if host.get('name'):
                  self.inventory.add_host(host['name'])
  
                  # create directly mapped groups
                  group_name = host.get('hostgroup_title', host.get('hostgroup_name'))
                  if group_name:
                      group_name = to_safe_group_name('%s%s' % (self.get_option('group_prefix'), group_name.lower().replace(" ", "")))
                      group_name = self.inventory.add_group(group_name)
                      self.inventory.add_child(group_name, host['name'])
  
                  # set host vars from host info
                  try:
                      for k, v in host.items():
                          if k not in ('name', 'hostgroup_title', 'hostgroup_name'):
                              try:
                                  self.inventory.set_variable(host['name'], self.get_option('vars_prefix') + k, v)
                              except ValueError as e:
                                  self.display.warning("Could not set host info hostvar for %s, skipping %s: %s" % (host, k, to_native(e)))
                  except ValueError as e:
                      self.display.warning("Could not get host info for %s, skipping: %s" % (host['name'], to_native(e)))
  
                  # set host vars from params
                  if self.get_option('want_params'):
                      for p in self._get_all_params_by_id(host['id']):
                          try:
                              self.inventory.set_variable(host['name'], p['name'], p['value'])
                          except ValueError as e:
                              self.display.warning("Could not set hostvar %s to '%s' for the '%s' host, skipping:  %s" %
                                                   (p['name'], to_native(p['value']), host, to_native(e)))
  
                  # set host vars from facts
                  if self.get_option('want_facts'):
                      self.inventory.set_variable(host['name'], 'ansible_facts', self._get_facts(host))
  
      def parse(self, inventory, loader, path, cache=True):
  
          super(InventoryModule, self).parse(inventory, loader, path)
  
          # read config from file, this sets 'options'
          self._read_config_data(path)
  
          # get connection host
          self.foreman_url = self.get_option('url')
          self.cache_key = self.get_cache_key(path)
          self.use_cache = cache and self.get_option('cache')
  
          # actually populate inventory
          self._populate()
  