Skip to content
Snippets Groups Projects
Commit 0d9873b8 authored by Denis Karpelevich's avatar Denis Karpelevich Committed by Thomas Woerner
Browse files

Allow multiple services creation


Adding an option to create multiple services in one go.
Adding tests (present/absent/without_skip_host_check)

Copied from PR #1054

Signed-off-by: default avatarDenis Karpelevich <dkarpele@redhat.com>
parent 180afd75
No related branches found
No related tags found
No related merge requests found
...@@ -45,6 +45,127 @@ options: ...@@ -45,6 +45,127 @@ options:
elements: str elements: str
required: true required: true
aliases: ["service"] aliases: ["service"]
services:
description: The list of service dicts.
type: list
elements: dict
suboptions:
name:
description: The service to manage
type: str
required: true
aliases: ["service"]
certificate:
description: Base-64 encoded service certificate.
required: false
type: list
elements: str
aliases: ["usercertificate"]
pac_type:
description: Supported PAC type.
required: false
choices: ["MS-PAC", "PAD", "NONE", ""]
type: list
elements: str
aliases: ["pac_type", "ipakrbauthzdata"]
auth_ind:
description: Defines an allow list for Authentication Indicators.
type: list
elements: str
required: false
choices: ["otp", "radius", "pkinit", "hardened", ""]
aliases: ["krbprincipalauthind"]
skip_host_check:
description: Skip checking if host object exists.
required: False
type: bool
force:
description: Force principal name even if host is not in DNS.
required: False
type: bool
requires_pre_auth:
description: Pre-authentication is required for the service.
required: false
type: bool
aliases: ["ipakrbrequirespreauth"]
ok_as_delegate:
description: Client credentials may be delegated to the service.
required: false
type: bool
aliases: ["ipakrbokasdelegate"]
ok_to_auth_as_delegate:
description: Allow service to authenticate on behalf of a client.
required: false
type: bool
aliases: ["ipakrboktoauthasdelegate"]
principal:
description: List of principal aliases for the service.
required: false
type: list
elements: str
aliases: ["krbprincipalname"]
smb:
description: Add a SMB service.
required: false
type: bool
netbiosname:
description: NETBIOS name for the SMB service.
required: false
type: str
host:
description: Host that can manage the service.
required: false
type: list
elements: str
aliases: ["managedby_host"]
allow_create_keytab_user:
description: Users allowed to create a keytab of this host.
required: false
type: list
elements: str
aliases: ["ipaallowedtoperform_write_keys_user"]
allow_create_keytab_group:
description: Groups allowed to create a keytab of this host.
required: false
type: list
elements: str
aliases: ["ipaallowedtoperform_write_keys_group"]
allow_create_keytab_host:
description: Hosts allowed to create a keytab of this host.
required: false
type: list
elements: str
aliases: ["ipaallowedtoperform_write_keys_host"]
allow_create_keytab_hostgroup:
description: Host group allowed to create a keytab of this host.
required: false
type: list
elements: str
aliases: ["ipaallowedtoperform_write_keys_hostgroup"]
allow_retrieve_keytab_user:
description: User allowed to retrieve a keytab of this host.
required: false
type: list
elements: str
aliases: ["ipaallowedtoperform_read_keys_user"]
allow_retrieve_keytab_group:
description: Groups allowed to retrieve a keytab of this host.
required: false
type: list
elements: str
aliases: ["ipaallowedtoperform_read_keys_group"]
allow_retrieve_keytab_host:
description: Hosts allowed to retrieve a keytab of this host.
required: false
type: list
elements: str
aliases: ["ipaallowedtoperform_read_keys_host"]
allow_retrieve_keytab_hostgroup:
description: Host groups allowed to retrieve a keytab of this host.
required: false
type: list
elements: str
aliases: ["ipaallowedtoperform_read_keys_hostgroup"]
certificate: certificate:
description: Base-64 encoded service certificate. description: Base-64 encoded service certificate.
required: false required: false
...@@ -239,6 +360,15 @@ EXAMPLES = """ ...@@ -239,6 +360,15 @@ EXAMPLES = """
- host1.example.com - host1.example.com
- host2.example.com - host2.example.com
action: member action: member
# Ensure multiple services are present.
- ipaservice:
ipaadmin_password: SomeADMINpassword
services:
- name: HTTP/www.example.com
host:
- host1.example.com
- name: HTTP/www.service.com
""" """
RETURN = """ RETURN = """
...@@ -248,6 +378,9 @@ from ansible.module_utils.ansible_freeipa_module import \ ...@@ -248,6 +378,9 @@ from ansible.module_utils.ansible_freeipa_module import \
IPAAnsibleModule, compare_args_ipa, encode_certificate, \ IPAAnsibleModule, compare_args_ipa, encode_certificate, \
gen_add_del_lists, gen_add_list, gen_intersection_list, ipalib_errors, \ gen_add_del_lists, gen_add_list, gen_intersection_list, ipalib_errors, \
api_get_realm, to_text api_get_realm, to_text
from ansible.module_utils import six
if six.PY3:
unicode = str
def find_service(module, name): def find_service(module, name):
...@@ -321,8 +454,9 @@ def check_parameters(module, state, action, names): ...@@ -321,8 +454,9 @@ def check_parameters(module, state, action, names):
'allow_retrieve_keytab_hostgroup'] 'allow_retrieve_keytab_hostgroup']
if state == 'present': if state == 'present':
if len(names) != 1: if names is not None and len(names) != 1:
module.fail_json(msg="Only one service can be added at a time.") module.fail_json(msg="Only one service can be added at a time "
"using 'name'.")
if action == 'service': if action == 'service':
invalid = ['delete_continue'] invalid = ['delete_continue']
...@@ -338,9 +472,6 @@ def check_parameters(module, state, action, names): ...@@ -338,9 +472,6 @@ def check_parameters(module, state, action, names):
invalid.append('delete_continue') invalid.append('delete_continue')
elif state == 'absent': elif state == 'absent':
if len(names) < 1:
module.fail_json(msg="No name given.")
if action == "service": if action == "service":
invalid.extend(invalid_not_member) invalid.extend(invalid_not_member)
else: else:
...@@ -360,11 +491,7 @@ def check_parameters(module, state, action, names): ...@@ -360,11 +491,7 @@ def check_parameters(module, state, action, names):
def init_ansible_module(): def init_ansible_module():
ansible_module = IPAAnsibleModule( service_spec = dict(
argument_spec=dict(
# general
name=dict(type="list", elements="str", aliases=["service"],
required=True),
# service attributesstr # service attributesstr
certificate=dict(type="list", elements="str", certificate=dict(type="list", elements="str",
aliases=['usercertificate'], aliases=['usercertificate'],
...@@ -414,13 +541,35 @@ def init_ansible_module(): ...@@ -414,13 +541,35 @@ def init_ansible_module():
aliases=['ipaallowedtoperform_read_keys_hostgroup']), aliases=['ipaallowedtoperform_read_keys_hostgroup']),
delete_continue=dict(type="bool", required=False, delete_continue=dict(type="bool", required=False,
aliases=['continue']), aliases=['continue']),
)
ansible_module = IPAAnsibleModule(
argument_spec=dict(
# general
name=dict(type="list", elements="str", aliases=["service"],
default=None, required=False),
services=dict(type="list",
default=None,
options=dict(
# Here name is a simple string
name=dict(type="str", required=True,
aliases=["service"]),
# Add service specific parameters
**service_spec
),
elements='dict',
required=False),
# action # action
action=dict(type="str", default="service", action=dict(type="str", default="service",
choices=["member", "service"]), choices=["member", "service"]),
# state # state
state=dict(type="str", default="present", state=dict(type="str", default="present",
choices=["present", "absent", "disabled"]), choices=["present", "absent", "disabled"]),
# Add service specific parameters for simple use case
**service_spec
), ),
mutually_exclusive=[["name", "services"]],
required_one_of=[["name", "services"]],
supports_check_mode=True, supports_check_mode=True,
) )
...@@ -436,6 +585,7 @@ def main(): ...@@ -436,6 +585,7 @@ def main():
# general # general
names = ansible_module.params_get("name") names = ansible_module.params_get("name")
services = ansible_module.params_get("services")
# service attributes # service attributes
principal = ansible_module.params_get("principal") principal = ansible_module.params_get("principal")
...@@ -462,8 +612,16 @@ def main(): ...@@ -462,8 +612,16 @@ def main():
state = ansible_module.params_get("state") state = ansible_module.params_get("state")
# check parameters # check parameters
if (names is None or len(names) < 1) and \
(services is None or len(services) < 1):
ansible_module.fail_json(msg="At least one name or services is "
"required")
check_parameters(ansible_module, state, action, names) check_parameters(ansible_module, state, action, names)
# Use services if names is None
if services is not None:
names = services
# Init # Init
changed = False changed = False
...@@ -480,8 +638,39 @@ def main(): ...@@ -480,8 +638,39 @@ def main():
commands = [] commands = []
keytab_members = ["user", "group", "host", "hostgroup"] keytab_members = ["user", "group", "host", "hostgroup"]
service_set = set()
for name in names: for service in names:
if isinstance(service, dict):
name = service.get("name")
if name in service_set:
ansible_module.fail_json(
msg="service '%s' is used more than once" % name)
service_set.add(name)
principal = service.get("principal")
certificate = service.get("certificate")
pac_type = service.get("pac_type")
auth_ind = service.get("auth_ind")
skip_host_check = service.get("skip_host_check")
if skip_host_check and not has_skip_host_check:
ansible_module.fail_json(
msg="Skipping host check is not supported by your IPA "
"version")
force = service.get("force")
requires_pre_auth = service.get("requires_pre_auth")
ok_as_delegate = service.get("ok_as_delegate")
ok_to_auth_as_delegate = service.get("ok_to_auth_as_delegate")
smb = service.get("smb")
netbiosname = service.get("netbiosname")
host = service.get("host")
delete_continue = service.get("delete_continue")
elif isinstance(service, (str, unicode)):
name = service
else:
ansible_module.fail_json(msg="Service '%s' is not valid" %
repr(service))
res_find = find_service(ansible_module, name) res_find = find_service(ansible_module, name)
res_principals = [] res_principals = []
......
---
- name: Create services.json
hosts: localhost
tasks:
- name: Check if services.json exists
ansible.builtin.stat:
path: services.json
register: register_stat_services
- name: Create services.json
ansible.builtin.command: /bin/bash services.sh 500
when: not register_stat_services.stat.exists
- name: Check if hosts.json exists
ansible.builtin.stat:
path: hosts.json
register: register_stat_hosts
- name: Create hosts.json
ansible.builtin.command: /bin/bash hosts.sh 500
when: not register_stat_hosts.stat.exists
#!/bin/bash -eu
NUM=${1-1000}
FILE="hosts.json"
echo "{" > "$FILE"
echo " \"host_list\": [" >> "$FILE"
for i in $(seq 1 "$NUM"); do
{
echo " {"
echo " \"name\": \"www.example$i.com\""
} >> "$FILE"
if [ "$i" -lt "$NUM" ]; then
echo " }," >> "$FILE"
else
echo " }" >> "$FILE"
fi
done
echo " ]" >> "$FILE"
echo "}" >> "$FILE"
#!/bin/bash -eu
NUM=${1-1000}
FILE="services.json"
echo "{" > "$FILE"
echo " \"service_list\": [" >> "$FILE"
for i in $(seq 1 "$NUM"); do
{
echo " {"
echo " \"name\": \"HTTP/www.example$i.com\","
echo " \"principal\": \"host/test.example$i.com\""
} >> "$FILE"
if [ "$i" -lt "$NUM" ]; then
echo " }," >> "$FILE"
else
echo " }" >> "$FILE"
fi
done
echo " ]" >> "$FILE"
echo "}" >> "$FILE"
#!/bin/bash -eu
NUM=1000
FILE="services_absent.json"
echo "{" > "$FILE"
echo " \"services\": [" >> "$FILE"
for i in $(seq 1 "$NUM"); do
echo " {" >> "$FILE"
echo " \"name\": \"HTTP/www.example$i.com\"," >> "$FILE"
if [ "$i" -lt "$NUM" ]; then
echo " }," >> "$FILE"
else
echo " }" >> "$FILE"
fi
done
echo " ]" >> "$FILE"
echo "}" >> "$FILE"
---
- name: Include create_services_json.yml
ansible.builtin.import_playbook: create_services_json.yml
- name: Test services absent
hosts: ipaserver
become: true
gather_facts: false
tasks:
- name: Include services.json
ansible.builtin.include_vars:
file: services.json # noqa 505
- name: Create dict with service names
ansible.builtin.set_fact:
services_names: "{{ services_names | default([]) + [{'name': item.name}] }}"
loop: "{{ service_list }}"
- name: Services absent len:{{ service_list | length }}
ipaservice:
ipaadmin_password: SomeADMINpassword
services: "{{ services_names }}"
state: absent
- name: Remove services.json
hosts: localhost
tasks:
- name: Remove services.json
ansible.builtin.file:
state: absent
path: services.json
---
- name: Include create_services_json.yml
ansible.builtin.import_playbook: create_services_json.yml
- name: Test services present
hosts: ipaserver
become: true
gather_facts: false
tasks:
- name: Include services.json
ansible.builtin.include_vars:
file: services.json # noqa 505
- name: Include hosts.json
ansible.builtin.include_vars:
file: hosts.json # noqa 505
- name: Hosts present len:{{ host_list | length }}
ipahost:
ipaadmin_password: SomeADMINpassword
hosts: "{{ host_list }}"
- name: Services present len:{{ service_list | length }}
ipaservice:
ipaadmin_password: SomeADMINpassword
services: "{{ service_list }}"
- name: Remove services.json
hosts: localhost
tasks:
- name: Remove services.json
ansible.builtin.file:
state: absent
path: services.json
- name: Remove hosts.json
ansible.builtin.file:
state: absent
path: hosts.json
---
- name: Include create_services_json.yml
ansible.builtin.import_playbook: create_services_json.yml
- name: Test services present slice
hosts: ipaserver
become: true
gather_facts: false
vars:
slice_size: 500
tasks:
- name: Include services.json
ansible.builtin.include_vars:
file: services.json # noqa 505
- name: Include hosts.json
ansible.builtin.include_vars:
file: hosts.json # noqa 505
- name: Size of services slice.
ansible.builtin.debug:
msg: "{{ service_list | length }}"
- name: Size of hosts slice.
ansible.builtin.debug:
msg: "{{ host_list | length }}"
- name: Hosts present
ipahost:
ipaadmin_password: SomeADMINpassword
hosts: "{{ host_list[item : item + slice_size] }}"
loop: "{{ range(0, service_list | length, slice_size) | list }}"
- name: Services present
ipaservice:
ipaadmin_password: SomeADMINpassword
services: "{{ service_list[item : item + slice_size] }}"
loop: "{{ range(0, service_list | length, slice_size) | list }}"
- name: Remove services.json
hosts: localhost
tasks:
- name: Remove services.json
ansible.builtin.file:
state: absent
path: services.json
- name: Remove hosts.json
ansible.builtin.file:
state: absent
path: hosts.json
---
- name: Test services without using option skip_host_check
hosts: ipaserver
become: true
tasks:
# setup
- name: Test services without using option skip_host_check
block:
- name: Setup test environment
ansible.builtin.include_tasks: env_setup.yml
- name: Services are present
ipaservice:
ipaadmin_password: SomeADMINpassword
services:
- name: "HTTP/{{ svc_fqdn }}"
principal:
- host/test.example.com
- name: "mysvc/{{ host1_fqdn }}"
pac_type: NONE
ok_as_delegate: yes
ok_to_auth_as_delegate: yes
- name: "HTTP/{{ host1_fqdn }}"
allow_create_keytab_user:
- user01
- user02
allow_create_keytab_group:
- group01
- group02
allow_create_keytab_host:
- "{{ host1_fqdn }}"
- "{{ host2_fqdn }}"
allow_create_keytab_hostgroup:
- hostgroup01
- hostgroup02
- name: "mysvc/{{ host2_fqdn }}"
auth_ind: otp,radius
register: result
failed_when: not result.changed or result.failed
- name: Services are present again
ipaservice:
ipaadmin_password: SomeADMINpassword
services:
- name: "HTTP/{{ svc_fqdn }}"
- name: "mysvc/{{ host1_fqdn }}"
- name: "HTTP/{{ host1_fqdn }}"
- name: "mysvc/{{ host2_fqdn }}"
register: result
failed_when: result.changed or result.failed
# failed_when: not result.failed has been added as this test needs to
# fail because two services with the same name should be added in the same
# task.
- name: Duplicate names in services failure test
ipaservice:
ipaadmin_password: SomeADMINpassword
services:
- name: "HTTP/{{ svc_fqdn }}"
- name: "mysvc/{{ host1_fqdn }}"
- name: "HTTP/{{ nohost_fqdn }}"
- name: "HTTP/{{ svc_fqdn }}"
register: result
failed_when: result.changed or not result.failed or "is used more than once" not in result.msg
- name: Services/name and name 'service' present
ipaservice:
ipaadmin_password: SomeADMINpassword
name: "HTTP/{{ svc_fqdn }}"
services:
- name: "HTTP/{{ svc_fqdn }}"
register: result
failed_when: result.changed or not result.failed or "parameters are mutually exclusive" not in result.msg
- name: Services/name and name are absent
ipaservice:
ipaadmin_password: SomeADMINpassword
register: result
failed_when: result.changed or not result.failed or "one of the following is required" not in result.msg
- name: Name is absent
ipaservice:
ipaadmin_password: SomeADMINpassword
name:
register: result
failed_when: result.changed or not result.failed or "At least one name or services is required" not in result.msg
- name: Only one service can be added at a time using name.
ipaservice:
ipaadmin_password: SomeADMINpassword
name: example.com,example1.com
register: result
failed_when: result.changed or not result.failed or "Only one service can be added at a time using 'name'." not in result.msg
always:
# cleanup
- name: Cleanup test environment
ansible.builtin.include_tasks: env_cleanup.yml
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment