Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New inventory plugin #1231

Merged
merged 2 commits into from
May 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
106 changes: 106 additions & 0 deletions README-inventory-plugin-freeipa.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
Inventory plugin
================

Description
-----------


The inventory plugin compiles a dynamic inventory from IPA domain. The servers can be filtered by their role(s).

This plugin is using the Python requests binding, that is only available for Python 3.7 and up.


Features
--------
* Dynamic inventory


Supported FreeIPA Versions
--------------------------

FreeIPA versions 4.6.0 and up are supported by the inventory plugin.


Requirements
------------

**Controller**
* Ansible version: 2.13+

**Node**
* Supported FreeIPA version (see above)


Configuration
=============

The inventory plugin is automatically enabled from the Ansible collection or from the top directory of the git repo if the `plugins` folder is linked to `~/.ansible`.

If `ansible.cfg` was modified to point to the roles and modules with `roles_path`, `library` and `module_utils` tag, then it is needed to set `inventory_plugins` also:

```
inventory_plugins = /my/dir/ansible-freeipa/plugins/inventory
```

Usage
=====

Example inventory file "freeipa.yml":

```yml
---
plugin: freeipa
server: server.ipa.local
ipaadmin_password: SomeADMINpassword
```

Example inventory file "freeipa.yml" with server TLS certificate verification using local copy of `/etc/ipa/ca.crt` from the server:

```yml
---
plugin: freeipa
server: server.ipa.local
ipaadmin_password: SomeADMINpassword
verify: ca.crt
```


How to use the plugin
---------------------

With the `ansible-inventory` command it is possible to show the generated inventorey:

```bash
ansible-inventory -v -i freeipa.yml --graph
```

Example inventory file "freeipa.yml" for use with `playbooks/config/retrieve-config.yml`:

```yml
---
plugin: freeipa
server: server.ipa.local
ipaadmin_password: SomeADMINpassword
inventory_group: ipaserver
```

```bash
ansible-playbook -u root -i ipa.yml playbooks/config/retrieve-config.yml
```

Variables
=========

Variable | Description | Required
-------- | ----------- | --------
`ipaadmin_principal` | The admin principal is a string and defaults to `admin` | no
`ipaadmin_password` | The admin password is a string and is required if there is no admin ticket available on the node | no
`server` | The FQDN of server to start the scan. (string) | yes
`verify` | The server TLS certificate file for verification (/etc/ipa/ca.crt). Turned off if not set. (string) | yes
`role` | The role(s) of the server. If several roles are given, only servers that have all the roles are returned. (list of strings) (choices: "IPA master", "CA server", "KRA server", "DNS server", "AD trust controller", "AD trust agent") | no
`inventory_group` | The inventory group to create. The default group name is "ipaservers". | no

Authors
=======

- Thomas Woerner
13 changes: 10 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ Features
* Repair mode for clients
* Backup and restore, also to and from controller
* Smartcard setup for servers and clients
* Inventory plugin freeipa
* Modules for automembership rule management
* Modules for automount key management
* Modules for automount location management
Expand Down Expand Up @@ -108,9 +109,10 @@ You can use the roles directly within the top directory of the git repo, but to
You can either adapt ansible.cfg:

```
roles_path = /my/dir/ansible-freeipa/roles
library = /my/dir/ansible-freeipa/plugins/modules
module_utils = /my/dir/ansible-freeipa/plugins/module_utils
roles_path = /my/dir/ansible-freeipa/roles
library = /my/dir/ansible-freeipa/plugins/modules
module_utils = /my/dir/ansible-freeipa/plugins/module_utils
inventory_plugins = /my/dir/ansible-freeipa/plugins/inventory
```

Or you can link the directories:
Expand Down Expand Up @@ -470,3 +472,8 @@ Modules in plugin/modules
* [ipavault](README-vault.md)

If you want to write a new module please read [writing a new module](plugins/modules/README.md).

Inventory plugins in plugin/inventory
=====================================

* [freeipa](README-inventory-plugin-freeipa.md)
180 changes: 180 additions & 0 deletions plugins/inventory/freeipa.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
# -*- coding: utf-8 -*-

# Authors:
# Thomas Woerner <twoerner@redhat.com>
#
# Copyright (C) 2024 Red Hat
# see file 'COPYING' for use and warranty information
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.

from __future__ import (absolute_import, division, print_function)

__metaclass__ = type

ANSIBLE_METADATA = {
"metadata_version": "1.0",
"supported_by": "community",
"status": ["preview"],
}

DOCUMENTATION = """
---
name: freeipa
plugin_type: inventory
version_added: "1.13"
short_description: Compiles a dynamic inventory from IPA domain
description: |
Compiles a dynamic inventory from IPA domain, filters servers by role(s).
options:
plugin:
description: Marks this as an instance of the "freeipa" plugin.
required: True
choices: ["freeipa"]
ipaadmin_principal:
description: The admin principal.
default: admin
type: str
ipaadmin_password:
description: The admin password.
required: true
type: str
server:
description: FQDN of server to start the scan.
type: str
required: true
verify:
description: |
The server TLS certificate file for verification (/etc/ipa/ca.crt).
Turned off if not set.
type: str
required: false
role:
description: |
The role(s) of the server. If several roles are given, only servers
that have all the roles are returned.
type: list
elements: str
choices: ["IPA master", "CA server", "KRA server", "DNS server",
"AD trust controller", "AD trust agent"]
required: false
inventory_group:
description: |
The inventory group to create. The default group name is "ipaservers".
type: str
default: ipaservers
author:
- Thomas Woerner (@t-woerner)
"""

EXAMPLES = """
# inventory.config file in YAML format
plugin: freeipa
server: ipaserver-01.ipa.local
ipaadmin_password: SomeADMINpassword

# inventory.config file in YAML format with server TLS certificate verification
plugin: freeipa
server: ipaserver-01.ipa.local
ipaadmin_password: SomeADMINpassword
verify: ca.crt
"""

import os
import requests
try:
from requests.packages import urllib3
except ImportError:
import urllib3
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

from ansible import constants
from ansible.errors import AnsibleParserError
from ansible.module_utils.common.text.converters import to_native
from ansible.plugins.inventory import BaseInventoryPlugin
from ansible.module_utils.six.moves.urllib.parse import quote


class InventoryModule(BaseInventoryPlugin):

NAME = 'freeipa'

def verify_file(self, path):
# pylint: disable=super-with-arguments
if super(InventoryModule, self).verify_file(path):
_name, ext = os.path.splitext(path)
if ext in constants.YAML_FILENAME_EXTENSIONS:
return True
return False

def parse(self, inventory, loader, path, cache=False):
# pylint: disable=super-with-arguments
super(InventoryModule, self).parse(inventory, loader, path,
cache=cache)
self._read_config_data(path) # This also loads the cache

self.get_option("plugin")
ipaadmin_principal = self.get_option("ipaadmin_principal")
ipaadmin_password = self.get_option("ipaadmin_password")
server = self.get_option("server")
verify = self.get_option("verify")
role = self.get_option("role")
inventory_group = self.get_option("inventory_group")

if verify is not None:
if not os.path.exists(verify):
raise AnsibleParserError("ERROR: Could not load %s" % verify)
else:
verify = False

self.inventory.add_group(inventory_group)

ipa_url = "https://%s/ipa" % server

s = requests.Session()
s.headers.update({"referer": ipa_url})
s.headers.update({"Content-Type":
"application/x-www-form-urlencoded"})
s.headers.update({"Accept": "text/plain"})

data = 'user=%s&password=%s' % (quote(ipaadmin_principal, safe=''),
quote(ipaadmin_password, safe=''))
response = s.post("%s/session/login_password" % ipa_url,
data=data, verify=verify)

# Now use json API
s.headers.update({"Content-Type": "application/json"})

kw_args = {}
if role is not None:
kw_args["servrole"] = role
json_data = {
"method" : "server_find",
"params": [[], kw_args],
"id": 0
}
response = s.post("%s/session/json" % ipa_url, json=json_data,
verify=verify)
json_res = response.json()

error = json_res.get("error")
if error is not None:
raise AnsibleParserError("ERROR: %s" % to_native(error))

if "result" in json_res and "result" in json_res["result"]:
res = json_res["result"].get("result")
if isinstance(res, list):
for server in res:
self.inventory.add_host(server["cn"][0],
group=inventory_group)
10 changes: 9 additions & 1 deletion utils/build-galaxy-release.sh
Original file line number Diff line number Diff line change
Expand Up @@ -125,9 +125,10 @@ sed -i -e "s/namespace: .*/namespace: \"$namespace\"/" galaxy.yml
sed -i -e "s/name: .*/name: \"$name\"/" galaxy.yml

find . -name "*~" -exec rm {} \;
find . -name "__py*__" -exec rm -rf {} \;


if [ $offline != 1 ]; then
if [ "$offline" != "" ]; then
echo "Creating CHANGELOG.rst..."
"$(dirname "$0")/changelog" --galaxy > CHANGELOG.rst
echo -e "\033[ACreating CHANGELOG.rst... \033[32;1mDONE\033[0m"
Expand Down Expand Up @@ -155,6 +156,13 @@ python utils/create_action_group.py "meta/runtime.yml" "$collection_prefix"
# ln -sf ../../roles/*/action_plugins/*.py .
#})

# Adapt inventory plugin and inventory plugin README
echo "Fixing inventory plugin and doc..."
sed -i -e "s/plugin: freeipa/plugin: ${collection_prefix}.freeipa/g" plugins/inventory/freeipa.py
sed -i -e "s/choices: \[\"freeipa\"\]/choices: \[\"${collection_prefix}.freeipa\"\]/g" plugins/inventory/freeipa.py
sed -i -e "s/plugin: freeipa/plugin: ${collection_prefix}.freeipa/g" README-inventory-plugin-freeipa.md
echo -e "\033[AFixing inventory plugin and doc... \033[32;1mDONE\033[0m"

for doc_fragment in plugins/doc_fragments/*.py; do
fragment=$(basename -s .py "$doc_fragment")

Expand Down