Skip to content
GitLab
Explore
Sign in
Primary navigation
Search or go to…
Project
A
Ansible FreeIPA
Manage
Activity
Members
Code
Repository
Branches
Commits
Tags
Repository graph
Compare revisions
Deploy
Model registry
Help
Help
Support
GitLab documentation
Compare GitLab plans
Community forum
Contribute to GitLab
Provide feedback
Keyboard shortcuts
?
Snippets
Groups
Projects
Show more breadcrumbs
Mirror
Ansible FreeIPA
Commits
e76047ed
Unverified
Commit
e76047ed
authored
5 years ago
by
Sergio Oliveira Campos
Browse files
Options
Downloads
Patches
Plain Diff
Created FreeIPABaseModule class to facilitate creation of new modules
parent
b211b50b
No related branches found
No related tags found
No related merge requests found
Changes
1
Hide whitespace changes
Inline
Side-by-side
Showing
1 changed file
plugins/module_utils/ansible_freeipa_module.py
+340
-9
340 additions, 9 deletions
plugins/module_utils/ansible_freeipa_module.py
with
340 additions
and
9 deletions
plugins/module_utils/ansible_freeipa_module.py
+
340
−
9
View file @
e76047ed
...
@@ -2,6 +2,7 @@
...
@@ -2,6 +2,7 @@
# -*- coding: utf-8 -*-
# -*- coding: utf-8 -*-
# Authors:
# Authors:
# Sergio Oliveira Campos <seocam@redhat.com>
# Thomas Woerner <twoerner@redhat.com>
# Thomas Woerner <twoerner@redhat.com>
#
#
# Copyright (C) 2019 Red Hat
# Copyright (C) 2019 Red Hat
...
@@ -27,10 +28,12 @@ import tempfile
...
@@ -27,10 +28,12 @@ import tempfile
import
shutil
import
shutil
import
gssapi
import
gssapi
from
datetime
import
datetime
from
datetime
import
datetime
from
pprint
import
pformat
from
ipalib
import
api
from
ipalib
import
api
from
ipalib
import
errors
as
ipalib_errors
from
ipalib
import
errors
as
ipalib_errors
# noqa
from
ipalib.config
import
Env
from
ipalib.config
import
Env
from
ipalib.constants
import
DEFAULT_CONFIG
,
LDAP_GENERALIZED_TIME_FORMAT
from
ipalib.constants
import
DEFAULT_CONFIG
,
LDAP_GENERALIZED_TIME_FORMAT
try
:
try
:
from
ipalib.install.kinit
import
kinit_password
,
kinit_keytab
from
ipalib.install.kinit
import
kinit_password
,
kinit_keytab
except
ImportError
:
except
ImportError
:
...
@@ -38,7 +41,9 @@ except ImportError:
...
@@ -38,7 +41,9 @@ except ImportError:
from
ipapython.ipautil
import
run
from
ipapython.ipautil
import
run
from
ipaplatform.paths
import
paths
from
ipaplatform.paths
import
paths
from
ipalib.krb_utils
import
get_credentials_if_valid
from
ipalib.krb_utils
import
get_credentials_if_valid
from
ansible.module_utils.basic
import
AnsibleModule
from
ansible.module_utils._text
import
to_text
from
ansible.module_utils._text
import
to_text
try
:
try
:
from
ipalib.x509
import
Encoding
from
ipalib.x509
import
Encoding
except
ImportError
:
except
ImportError
:
...
@@ -52,7 +57,7 @@ if six.PY3:
...
@@ -52,7 +57,7 @@ if six.PY3:
unicode
=
str
unicode
=
str
def
valid_creds
(
module
,
principal
):
def
valid_creds
(
module
,
principal
):
# noqa
"""
"""
Get valid credintials matching the princial, try GSSAPI first
Get valid credintials matching the princial, try GSSAPI first
"""
"""
...
@@ -205,9 +210,24 @@ def date_format(value):
...
@@ -205,9 +210,24 @@ def date_format(value):
raise
ValueError
(
"
Invalid date
'
%s
'"
%
value
)
raise
ValueError
(
"
Invalid date
'
%s
'"
%
value
)
def
compare_args_ipa
(
module
,
args
,
ipa
):
def
compare_args_ipa
(
module
,
args
,
ipa
):
# noqa
"""
Compare IPA obj attrs with the command args.
This function compares IPA objects attributes with the args the
module is intending to use to call a command. This is useful to know
if call to IPA server will be needed or not.
In other to compare we have to prepare the perform slight changes in
data formats.
Returns True if they are the same and False otherwise.
"""
base_debug_msg
=
"
Ansible arguments and IPA commands differed.
"
for
key
in
args
.
keys
():
for
key
in
args
.
keys
():
if
key
not
in
ipa
:
if
key
not
in
ipa
:
module
.
debug
(
base_debug_msg
+
"
Command key not present in IPA: %s
"
%
key
)
return
False
return
False
else
:
else
:
arg
=
args
[
key
]
arg
=
args
[
key
]
...
@@ -220,25 +240,35 @@ def compare_args_ipa(module, args, ipa):
...
@@ -220,25 +240,35 @@ def compare_args_ipa(module, args, ipa):
if
isinstance
(
ipa_arg
,
list
):
if
isinstance
(
ipa_arg
,
list
):
if
not
isinstance
(
arg
,
list
):
if
not
isinstance
(
arg
,
list
):
arg
=
[
arg
]
arg
=
[
arg
]
if
len
(
ipa_arg
)
!=
len
(
arg
):
module
.
debug
(
base_debug_msg
+
"
List length doesn
'
t match for key %s: %d %d
"
%
(
key
,
len
(
arg
),
len
(
ipa_arg
),)
)
return
False
if
isinstance
(
ipa_arg
[
0
],
str
)
and
isinstance
(
arg
[
0
],
int
):
if
isinstance
(
ipa_arg
[
0
],
str
)
and
isinstance
(
arg
[
0
],
int
):
arg
=
[
to_text
(
_arg
)
for
_arg
in
arg
]
arg
=
[
to_text
(
_arg
)
for
_arg
in
arg
]
if
isinstance
(
ipa_arg
[
0
],
unicode
)
and
isinstance
(
arg
[
0
],
int
):
if
isinstance
(
ipa_arg
[
0
],
unicode
)
and
isinstance
(
arg
[
0
],
int
):
arg
=
[
to_text
(
_arg
)
for
_arg
in
arg
]
arg
=
[
to_text
(
_arg
)
for
_arg
in
arg
]
# module.warn("%s <=> %s" % (repr(arg), repr(ipa_arg)))
try
:
try
:
arg_set
=
set
(
arg
)
arg_set
=
set
(
arg
)
ipa_arg_set
=
set
(
ipa_arg
)
ipa_arg_set
=
set
(
ipa_arg
)
except
TypeError
:
except
TypeError
:
if
arg
!=
ipa_arg
:
if
arg
!=
ipa_arg
:
# module.warn("%s != %s" % (repr(arg), repr(ipa_arg)))
module
.
debug
(
base_debug_msg
+
"
Different values: %s %s
"
%
(
arg
,
ipa_arg
)
)
return
False
return
False
else
:
else
:
if
arg_set
!=
ipa_arg_set
:
if
arg_set
!=
ipa_arg_set
:
# module.warn("%s != %s" % (repr(arg), repr(ipa_arg)))
module
.
debug
(
base_debug_msg
+
"
Different set content: %s %s
"
%
(
arg_set
,
ipa_arg_set
,)
)
return
False
return
False
# module.warn("%s == %s" % (repr(arg), repr(ipa_arg)))
return
True
return
True
...
@@ -289,6 +319,16 @@ def encode_certificate(cert):
...
@@ -289,6 +319,16 @@ def encode_certificate(cert):
return
encoded
return
encoded
def
is_valid_port
(
port
):
if
not
isinstance
(
port
,
int
):
return
False
if
1
<=
port
<=
65535
:
return
True
return
False
def
is_ipv4_addr
(
ipaddr
):
def
is_ipv4_addr
(
ipaddr
):
"""
"""
Test if figen IP address is a valid IPv4 address
Test if figen IP address is a valid IPv4 address
...
@@ -309,3 +349,294 @@ def is_ipv6_addr(ipaddr):
...
@@ -309,3 +349,294 @@ def is_ipv6_addr(ipaddr):
except
socket
.
error
:
except
socket
.
error
:
return
False
return
False
return
True
return
True
class
AnsibleFreeIPAParams
(
dict
):
def
__init__
(
self
,
ansible_module
):
self
.
update
(
ansible_module
.
params
)
self
.
ansible_module
=
ansible_module
@property
def
names
(
self
):
return
self
.
name
def
__getattr__
(
self
,
name
):
param
=
self
.
get
(
name
)
if
param
is
not
None
:
return
_afm_convert
(
param
)
class
FreeIPABaseModule
(
AnsibleModule
):
"""
Base class for FreeIPA Ansible modules.
Provides methods useful methods to be used by our modules.
This class should be overriten and instantiated for the module.
A basic implementation of an Ansible FreeIPA module expects its
class to:
1. Define a class attribute ``ipa_param_mapping``
2. Implement the method ``define_ipa_commands()``
3. Implement the method ``check_ipa_params()`` (optional)
After instantiating the class the method ``ipa_run()`` should be called.
Example (ansible-freeipa/plugins/modules/ipasomemodule.py):
class SomeIPAModule(FreeIPABaseModule):
ipa_param_mapping = {
"
arg_to_be_passed_to_ipa_command
"
:
"
module_param
"
,
"
another_arg
"
:
"
get_another_module_param
"
,
}
def get_another_module_param(self):
another_module_param = self.ipa_params.another_module_param
# Validate or modify another_module_param
# ...
return another_module_param
def check_ipa_params(self):
# Validate your params here
# Example:
if not self.ipa_params.module_param in VALID_OPTIONS:
self.fail_json(msg=
"
Invalid value for argument module_param
"
)
def define_ipa_commands(self):
args = self.get_ipa_command_args()
self.add_ipa_command(
"
some_ipa_command
"
,
name=
"
obj-name
"
,
args=args,
)
def main():
ipa_module = SomeIPAModule(argument_spec=dict(
module_param=dict(
type=
"
str
"
,
default=None,
required=False,
),
another_module_param=dict(
type=
"
str
"
,
default=None,
required=False,
),
))
ipa_module.ipa_run()
if __name__ ==
"
__main__
"
:
main()
"""
ipa_param_mapping
=
None
def
__init__
(
self
,
*
args
,
**
kwargs
):
super
(
FreeIPABaseModule
,
self
).
__init__
(
*
args
,
**
kwargs
)
# Attributes to store kerberos credentials (if needed)
self
.
ccache_dir
=
None
self
.
ccache_name
=
None
# Status of an execution. Will be changed to True
# if something is actually peformed.
self
.
changed
=
False
# Status of the connection with the IPA server.
# We need to know if the connection was actually stablished
# before we start sending commands.
self
.
ipa_connected
=
False
# Commands to be executed
self
.
ipa_commands
=
[]
# Module exit arguments.
self
.
exit_args
=
{}
# Wrapper around the AnsibleModule.params.
# Return the actual params but performing transformations
# when needed.
self
.
ipa_params
=
AnsibleFreeIPAParams
(
self
)
def
get_ipa_command_args
(
self
):
"""
Return a dict to be passed to an IPA command.
The keys of ``ipa_param_mapping`` are also the keys of the return dict.
The values of ``ipa_param_mapping`` needs to be either:
* A str with the name of a defined method; or
* A key of ``AnsibleModule.param``.
In case of a method the return of the method will be set as value
for the return dict.
In case of a AnsibleModule.param the value of the param will be
set in the return dict. In addition to that boolean values will be
automaticaly converted to uppercase strings (as required by FreeIPA
server).
"""
args
=
{}
for
ipa_param_name
,
param_name
in
self
.
ipa_param_mapping
.
items
():
# Check if param_name is actually a param
if
param_name
in
self
.
ipa_params
:
value
=
self
.
ipa_params
.
get
(
param_name
)
if
isinstance
(
value
,
bool
):
value
=
"
TRUE
"
if
value
else
"
FALSE
"
# Since param wasn't a param check if it's a method name
elif
hasattr
(
self
,
param_name
):
method
=
getattr
(
self
,
param_name
)
if
callable
(
method
):
value
=
method
()
# We don't have a way to guess the value so fail.
else
:
self
.
fail_json
(
msg
=
(
"
Couldn
'
t get a value for
'
%s
'
. Option
'
%s
'
is not
"
"
a module argument neither a defined method.
"
)
%
(
ipa_param_name
,
param_name
)
)
if
value
is
not
None
:
args
[
ipa_param_name
]
=
value
return
args
def
check_ipa_params
(
self
):
"""
Validate ipa_params before command is called.
"""
pass
def
define_ipa_commands
(
self
):
"""
Define commands that will be run in IPA server.
"""
raise
NotImplementedError
def
api_command
(
self
,
command
,
name
=
None
,
args
=
None
):
"""
Execute a single command in IPA server.
"""
if
args
is
None
:
args
=
{}
if
name
is
None
:
return
api_command_no_name
(
self
,
command
,
args
)
return
api_command
(
self
,
command
,
name
,
args
)
def
__enter__
(
self
):
"""
Connect to IPA server.
Check the there are working Kerberos credentials to connect to
IPA server. If there are not we perform a temporary kinit
that will be terminated when exiting the context.
If the connection fails ``ipa_connected`` attribute will be set
to False.
"""
principal
=
self
.
ipa_params
.
ipaadmin_principal
password
=
self
.
ipa_params
.
ipaadmin_password
try
:
if
not
valid_creds
(
self
,
principal
):
self
.
ccache_dir
,
self
.
ccache_name
=
temp_kinit
(
principal
,
password
,
)
api_connect
()
except
Exception
as
excpt
:
self
.
fail_json
(
msg
=
str
(
excpt
))
else
:
self
.
ipa_connected
=
True
return
self
def
__exit__
(
self
,
exc_type
,
exc_val
,
exc_tb
):
"""
Terminate a connection with the IPA server.
Deal with exceptions, destroy temporary kinit credentials and
exit the module with proper arguments.
"""
if
exc_val
:
self
.
fail_json
(
msg
=
str
(
exc_val
))
# TODO: shouldn't we also disconnect from api backend?
temp_kdestroy
(
self
.
ccache_dir
,
self
.
ccache_name
)
self
.
exit_json
(
changed
=
self
.
changed
,
user
=
self
.
exit_args
)
def
get_command_errors
(
self
,
command
,
result
):
"""
Look for erros into command results.
"""
# Get all errors
# All "already a member" and "not a member" failures in the
# result are ignored. All others are reported.
errors
=
[]
for
item
in
result
.
get
(
"
failed
"
,
tuple
()):
failed_item
=
result
[
"
failed
"
][
item
]
for
member_type
in
failed_item
:
for
member
,
failure
in
failed_item
[
member_type
]:
if
(
"
already a member
"
in
failure
or
"
not a member
"
in
failure
):
continue
errors
.
append
(
"
%s: %s %s: %s
"
%
(
command
,
member_type
,
member
,
failure
)
)
if
len
(
errors
)
>
0
:
self
.
fail_json
(
"
,
"
.
join
(
"
errors
"
))
def
add_ipa_command
(
self
,
command
,
name
=
None
,
args
=
None
):
"""
Add a command to the list of commands to be executed.
"""
self
.
ipa_commands
.
append
((
name
,
command
,
args
or
{}))
def
_run_ipa_commands
(
self
):
"""
Execute commands in self.ipa_commands.
"""
result
=
None
for
name
,
command
,
args
in
self
.
ipa_commands
:
try
:
result
=
self
.
api_command
(
command
,
name
,
args
)
except
Exception
as
excpt
:
self
.
fail_json
(
msg
=
"
%s: %s: %s
"
%
(
command
,
name
,
str
(
excpt
)))
else
:
if
"
completed
"
in
result
:
if
result
[
"
completed
"
]
>
0
:
self
.
changed
=
True
else
:
self
.
changed
=
True
self
.
get_command_errors
(
command
,
result
)
def
require_ipa_attrs_change
(
self
,
command_args
,
ipa_attrs
):
"""
Compare given args with current object attributes.
Returns True in case current IPA object attributes differ from
args passed to the module.
"""
equal
=
compare_args_ipa
(
self
,
command_args
,
ipa_attrs
)
return
not
equal
def
pdebug
(
self
,
value
):
"""
Debug with pretty formatting.
"""
self
.
debug
(
pformat
(
value
))
def
ipa_run
(
self
):
"""
Execute module actions.
"""
with
self
:
if
not
self
.
ipa_connected
:
return
self
.
check_ipa_params
()
self
.
define_ipa_commands
()
self
.
_run_ipa_commands
()
This diff is collapsed.
Click to expand it.
Preview
0%
Loading
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Save comment
Cancel
Please
register
or
sign in
to comment