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

Add foreman_user module #217

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions module_utils/ansible_nailgun_cement.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from nailgun.entities import (
_check_for_value,
ActivationKey,
AuthSourceLDAP,
Entity,
AbstractContentViewFilter,
CommonParameter,
Expand All @@ -32,11 +33,13 @@
Realm,
Repository,
RepositorySet,
Role,
Setting,
SmartProxy,
Subnet,
Subscription,
TemplateKind,
User,
AbstractComputeResource,
OSDefaultTemplate,
ComputeProfile,
Expand Down Expand Up @@ -440,6 +443,20 @@ def find_content_view_filter(module, name, content_view, failsafe=False):
return handle_find_response(module, content_view_filter.search(), message="No content view filter found for %s" % name, failsafe=failsafe)


def find_user(module, login, failsafe=False):
user = User(login=login).search(set(), {'search': 'login="{}"'.format(login)})
return handle_find_response(module, user, message="No user found for %s" % login, failsafe=failsafe)


def find_roles(module, roles):
return list(map(lambda role: find_role(module, role), roles))


def find_role(module, name, failsafe=False):
user = Role(name=name).search(set(), {'search': 'name="{}"'.format(name)})
return handle_find_response(module, user, message="No role found for %s" % name, failsafe=failsafe)


def find_organizations(module, organizations):
return list(map(lambda organization: find_organization(module, organization), organizations))

Expand All @@ -458,6 +475,11 @@ def find_location(module, name, failsafe=False):
return handle_find_response(module, loc, message="No location found for %s" % name, failsafe=failsafe)


def find_auth_source_ldap(module, name, failsafe=False):
auth_source = AuthSourceLDAP(name=name).search(set(), {'search': 'name="{}"'.format(name)})
return handle_find_response(module, auth_source, message="No authentication source found for %s" % name, failsafe=failsafe)


def find_compute_resource(module, name, failsafe=False):
compute_resource = AbstractComputeResource(name=name).search(set(), {'search': 'name="{}"'.format(name)})
return handle_find_response(module, compute_resource, message="No compute resource found for %s" % name, failsafe=failsafe)
Expand Down
250 changes: 250 additions & 0 deletions modules/foreman_user.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,250 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# (c) 2018, Ondřej Gajdušek [email protected]>
#
# This file is part of Ansible
#
# Ansible 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.
#
# Ansible 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 Ansible. If not, see <http://www.gnu.org/licenses/>.

DOCUMENTATION = '''
---
module: foreman_user
short_description: Manage Foreman User
description:
- Manage Foreman User
author:
- "Ondřej Gajdušek (@ogajduse)"
requirements:
- "nailgun >= 0.28.0"
- "python >= 2.6"
- "ansible >= 2.3"
options:
server_url:
description:
- URL of Foreman server
required: true
username:
description:
- Username on Foreman server
required: true
password:
description:
- Password for user accessing Foreman server
required: true
verify_ssl:
description:
- Verify SSL of the Foreman server
required: false
default: true
type: bool
login:
description:
- Login of the Foreman user
required: true
user_password:
description:
- Password for the given Foreman login
required: true
first_name:
description:
- First name of the user
required: false
last_name:
description:
- Last name of the user
required: false
admin:
description:
- Give admin rights to the user
required: false
default: false
type: bool
default_location:
description:
- Name of the default location the user is in
required: false
default_organization:
description:
- Name of the default organization the user is in
required: false
auth_source:
description:
- Authentication source the user is authorized by
default: INTERNAL
required: true
locations:
description:
- Locations that the user is in
required: false
default: None
type: list
organizations:
description:
- Organizations that the user is in
required: false
default: None
type: list
roles:
description:
- Roles to be assigned to the user
required: false
default: None
type: list
mail:
description:
- Mail of the user
required: false
'''

EXAMPLES = '''
- name: "Create a new User"
foreman_user:
username: "admin"
password: "changeme"
server_url: "https://foreman.example.com"
login: "exampleuser"
user_password: "secret"
first_name: "Example"
last_name: "User"
admin: false
default_location: "Default Location"
default_organization: "Default Organization"
auth_source: "INTERNAL"
locations:
- "Default Location"
- "My Cool New Location"
organizations:
- "Default Location"
mail: "[email protected]"
state: present
'''

RETURN = '''# '''

try:
from ansible.module_utils.ansible_nailgun_cement import (
create_server,
ping_server,
find_user,
naildown_entity_state,
sanitize_entity_dict,
find_auth_source_ldap,
find_organizations,
find_organization,
find_roles,
find_locations,
find_location,
)
from nailgun.entities import User

has_import_error = False
except ImportError as e:
has_import_error = True
import_error_msg = str(e)


from ansible.module_utils.basic import AnsibleModule


# This is the only true source for names (and conversions thereof)
name_map = {
'login': 'login',
'first_name': 'firstname',
'last_name': 'lastname',
'mail': 'mail',
'admin': 'admin',
'user_password': 'password',
'default_location': 'default_location',
'default_organization': 'default_organization',
'auth_source': 'auth_source',
'locations': 'location',
'organizations': 'organization',
'roles': 'role',
}


def main():
module = AnsibleModule(
argument_spec=dict(
server_url=dict(required=True),
username=dict(required=True, no_log=True),
password=dict(required=True, no_log=True),
verify_ssl=dict(type='bool', default=True),
login=dict(required=True),
first_name=dict(),
last_name=dict(),
mail=dict(),
admin=dict(type='bool', default=False),
user_password=dict(required=True, no_log=True),
default_location=dict(),
default_organization=dict(),
auth_source=dict(default='INTERNAL'),
locations=dict(type='list'),
organizations=dict(type='list'),
roles=dict(type='list'),
state=dict(default='present', choices=['present', 'absent']),
),
supports_check_mode=True,
)

if has_import_error:
module.fail_json(msg=import_error_msg)

entity_dict = dict(
[(k, v) for (k, v) in module.params.items() if v is not None])

server_url = entity_dict.pop('server_url')
username = entity_dict.pop('username')
password = entity_dict.pop('password')
verify_ssl = entity_dict.pop('verify_ssl')
state = entity_dict.pop('state')

try:
create_server(server_url, (username, password), verify_ssl)
except Exception as e:
module.fail_json(msg="Failed to connect to Foreman server: %s " % e)

ping_server(module)

if entity_dict['auth_source'] == 'INTERNAL':
entity_dict['auth_source'] = 1
else:
entity_dict['auth_source'] = find_auth_source_ldap(module, name=entity_dict['auth_source'])

if 'default_organization' in entity_dict:
entity_dict['default_organization'] = find_organization(module, name=entity_dict['default_organization'])

if 'default_location' in entity_dict:
entity_dict['default_location'] = find_location(module, name=entity_dict['default_location'])

if 'organizations' in entity_dict:
entity_dict['organizations'] = find_organizations(module, entity_dict['organizations'])

if 'locations' in entity_dict:
entity_dict['locations'] = find_locations(module, entity_dict['locations'])

if 'roles' in entity_dict:
entity_dict['roles'] = find_roles(module, entity_dict['roles'])

entity = find_user(module, login=entity_dict['login'], failsafe=True)

entity_dict = sanitize_entity_dict(entity_dict, name_map)

changed = naildown_entity_state(User, entity_dict, entity, state, module)

module.exit_json(changed=changed)


if __name__ == '__main__':
main()
1 change: 1 addition & 0 deletions test/test_crud.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
'subnet',
'sync_plan',
'upload',
'user',
]


Expand Down
37 changes: 37 additions & 0 deletions test/test_playbooks/tasks/user.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
---
- name: "Ensure user '{{ user_login }}' is {{ user_state }}"
vars:
- user_login: "test_user"
- user_firstname: "Test"
- user_lastname: "User"
- user_is_admin: "true"
- user_password: "secret"
- user_location: "Default Location"
- user_organization: "Default Organization"
- user_mail: "[email protected]"
foreman_user:
username: "{{ foreman_username }}"
password: "{{ foreman_password }}"
server_url: "{{ foreman_server_url }}"
verify_ssl: "{{ foreman_verify_ssl }}"
login: "{{ user_login }}"
first_name: "{{ user_firstname }}"
last_name: "{{ user_lastname }}"
admin: "{{ user_is_admin }}"
user_password: "{{ user_password }}"
default_location: "{{ user_location }}"
default_organization: "{{ user_organization }}"
auth_source: INTERNAL
locations:
- "{{ user_location }}"
organizations:
- "{{ user_organization }}"
roles:
- View hosts
mail: "{{ user_mail }}"
state: "{{ user_state }}"
register: result
- fail:
msg: "Ensuring user is {{ user_state }} failed! (expected_change: {{ expected_change | default('unknown') }})"
when: (expected_change is defined) and (result.changed != expected_change)
...
20 changes: 20 additions & 0 deletions test/test_playbooks/user.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
---
- hosts: tests
gather_facts: false
tasks:
- name: Load server config
include_vars:
file: server_vars.yml
- include: tasks/user.yml
vars:
user_state: present
expected_change: true
- include: tasks/user.yml
vars:
user_state: present
expected_change: false
- include: tasks/user.yml
vars:
user_state: absent
expected_change: true
...