# -*- coding: utf-8 -*-
# Copyright (c) 2018, Stefan Heitmueller <stefan.heitmueller@gmx.com>
# Copyright (c) 2018 Ansible Project
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later

from __future__ import (absolute_import, division, print_function)

__metaclass__ = type

DOCUMENTATION = '''
    name: gitlab_runners
    author:
      - Stefan Heitmüller (@morph027) <stefan.heitmueller@gmx.com>
    short_description: Ansible dynamic inventory plugin for GitLab runners.
    requirements:
        - python-gitlab > 1.8.0
    extends_documentation_fragment:
        - constructed
    description:
        - Reads inventories from the GitLab API.
        - Uses a YAML configuration file gitlab_runners.[yml|yaml].
    options:
        plugin:
            description: The name of this plugin, it should always be set to 'gitlab_runners' for this plugin to recognize it as it's own.
            type: str
            required: true
            choices:
              - gitlab_runners
              - community.general.gitlab_runners
        server_url:
            description: The URL of the GitLab server, with protocol (i.e. http or https).
            env:
              - name: GITLAB_SERVER_URL
                version_added: 1.0.0
            type: str
            required: true
        api_token:
            description: GitLab token for logging in.
            env:
              - name: GITLAB_API_TOKEN
                version_added: 1.0.0
            type: str
            aliases:
              - private_token
              - access_token
        filter:
            description: filter runners from GitLab API
            env:
              - name: GITLAB_FILTER
                version_added: 1.0.0
            type: str
            choices: ['active', 'paused', 'online', 'specific', 'shared']
        verbose_output:
            description: Toggle to (not) include all available nodes metadata
            type: bool
            default: true
'''

EXAMPLES = '''
# gitlab_runners.yml
plugin: community.general.gitlab_runners
host: https://gitlab.com

# Example using constructed features to create groups and set ansible_host
plugin: community.general.gitlab_runners
host: https://gitlab.com
strict: false
keyed_groups:
  # add e.g. amd64 hosts to an arch_amd64 group
  - prefix: arch
    key: 'architecture'
  # add e.g. linux hosts to an os_linux group
  - prefix: os
    key: 'platform'
  # create a group per runner tag
  # e.g. a runner tagged w/ "production" ends up in group "label_production"
  # hint: labels containing special characters will be converted to safe names
  - key: 'tag_list'
    prefix: tag
'''

from ansible.errors import AnsibleError, AnsibleParserError
from ansible.module_utils.common.text.converters import to_native
from ansible.plugins.inventory import BaseInventoryPlugin, Constructable
from ansible.utils.unsafe_proxy import wrap_var as make_unsafe

try:
    import gitlab
    HAS_GITLAB = True
except ImportError:
    HAS_GITLAB = False


class InventoryModule(BaseInventoryPlugin, Constructable):
    ''' Host inventory parser for ansible using GitLab API as source. '''

    NAME = 'community.general.gitlab_runners'

    def _populate(self):
        gl = gitlab.Gitlab(self.get_option('server_url'), private_token=self.get_option('api_token'))
        self.inventory.add_group('gitlab_runners')
        try:
            if self.get_option('filter'):
                runners = gl.runners.all(scope=self.get_option('filter'))
            else:
                runners = gl.runners.all()
            for runner in runners:
                host = make_unsafe(str(runner['id']))
                ip_address = runner['ip_address']
                host_attrs = make_unsafe(vars(gl.runners.get(runner['id']))['_attrs'])
                self.inventory.add_host(host, group='gitlab_runners')
                self.inventory.set_variable(host, 'ansible_host', make_unsafe(ip_address))
                if self.get_option('verbose_output', True):
                    self.inventory.set_variable(host, 'gitlab_runner_attributes', host_attrs)

                # Use constructed if applicable
                strict = self.get_option('strict')
                # Composed variables
                self._set_composite_vars(self.get_option('compose'), host_attrs, host, strict=strict)
                # Complex groups based on jinja2 conditionals, hosts that meet the conditional are added to group
                self._add_host_to_composed_groups(self.get_option('groups'), host_attrs, host, strict=strict)
                # Create groups based on variable values and add the corresponding hosts to it
                self._add_host_to_keyed_groups(self.get_option('keyed_groups'), host_attrs, host, strict=strict)
        except Exception as e:
            raise AnsibleParserError('Unable to fetch hosts from GitLab API, this was the original exception: %s' % to_native(e))

    def verify_file(self, path):
        """Return the possibly of a file being consumable by this plugin."""
        return (
            super(InventoryModule, self).verify_file(path) and
            path.endswith(("gitlab_runners.yaml", "gitlab_runners.yml")))

    def parse(self, inventory, loader, path, cache=True):
        if not HAS_GITLAB:
            raise AnsibleError('The GitLab runners dynamic inventory plugin requires python-gitlab: https://python-gitlab.readthedocs.io/en/stable/')
        super(InventoryModule, self).parse(inventory, loader, path, cache)
        self._read_config_data(path)
        self._populate()
