@ -79,6 +79,14 @@ options:
default : " yes "
choices : [ " yes " , " no " ]
version_added : " 2.1 "
comment :
description :
- Change the comment on the public key . Rewriting the comment is useful in
cases such as fetching it from GitHub or GitLab .
- If no comment is specified , the existing comment will be kept .
required : false
default : None
version_added : " 2.4 "
author : " Ansible Core Team "
'''
@ -220,6 +228,7 @@ from ansible.module_utils.basic import AnsibleModule
from ansible . module_utils . pycompat24 import get_exception
from ansible . module_utils . urls import fetch_url
class keydict ( dict ) :
""" a dictionary that maintains the order of keys as they are added
@ -247,8 +256,8 @@ class keydict(dict):
# http://stackoverflow.com/questions/2328235/pythonextend-the-dict-class
def __init__ ( self , * args , * * kw ) :
super ( keydict , self ) . __init__ ( * args , * * kw )
self . itemlist = list ( super ( keydict , self ) . keys ( ) )
super ( keydict , self ) . __init__ ( * args , * * kw )
self . itemlist = list ( super ( keydict , self ) . keys ( ) )
def __setitem__ ( self , key , value ) :
self . itemlist . append ( key )
@ -335,7 +344,7 @@ def keyfile(module, user, write=False, path=None, manage_dir=True):
if not os . path . exists ( basedir ) :
os . makedirs ( basedir )
try :
f = open ( keysfile , " w " ) # touches file so we can set ownership and perms
f = open ( keysfile , " w " ) # touches file so we can set ownership and perms
finally :
f . close ( )
if module . selinux_enabled ( ) :
@ -349,12 +358,13 @@ def keyfile(module, user, write=False, path=None, manage_dir=True):
return keysfile
def parseoptions ( module , options ) :
'''
reads a string containing ssh - key options
and returns a dictionary of those options
'''
options_dict = keydict ( ) # ordered dict
options_dict = keydict ( ) # ordered dict
if options :
# the following regex will split on commas while
# ignoring those commas that fall within quotes
@ -369,6 +379,7 @@ def parseoptions(module, options):
return options_dict
def parsekey ( module , raw_key , rank = None ) :
'''
parses a key , which may or may not contain a list
@ -398,7 +409,7 @@ def parsekey(module, raw_key, rank=None):
# split key safely
lex = shlex . shlex ( raw_key )
lex . quotes = [ ]
lex . commenters = ' ' # keep comment hashes
lex . commenters = ' ' # keep comment hashes
lex . whitespace_split = True
key_parts = list ( lex )
@ -430,6 +441,7 @@ def parsekey(module, raw_key, rank=None):
return ( key , key_type , options , comment , rank )
def readfile ( filename ) :
if not os . path . isfile ( filename ) :
@ -441,6 +453,7 @@ def readfile(filename):
finally :
f . close ( )
def parsekeys ( module , lines ) :
keys = { }
for rank_index , line in enumerate ( lines . splitlines ( True ) ) :
@ -454,10 +467,11 @@ def parsekeys(module, lines):
keys [ line ] = ( line , ' skipped ' , None , None , rank_index )
return keys
def writefile ( module , filename , content ) :
fd , tmp_path = tempfile . mkstemp ( ' ' , ' tmp ' , os . path . dirname ( filename ) )
f = open ( tmp_path , " w " )
f = open ( tmp_path , " w " )
try :
f . write ( content )
@ -467,6 +481,7 @@ def writefile(module, filename, content):
f . close ( )
module . atomic_move ( tmp_path , filename )
def serialize ( keys ) :
lines = [ ]
new_keys = keys . values ( )
@ -496,11 +511,12 @@ def serialize(keys):
key_line = key [ 0 ]
else :
key_line = " %s %s %s %s \n " % ( option_str , key_type , keyhash , comment )
except :
except Exception :
key_line = key
lines . append ( key_line )
return ' ' . join ( lines )
def enforce_state ( module , params ) :
"""
Add or remove key .
@ -513,6 +529,7 @@ def enforce_state(module, params):
state = params . get ( " state " , " present " )
key_options = params . get ( " key_options " , None )
exclusive = params . get ( " exclusive " , False )
comment = params . get ( " comment " , None )
error_msg = " Error getting key from: %s "
# if the key is a url, request it and use it as key source
@ -559,6 +576,9 @@ def enforce_state(module, params):
# rank here is the rank in the provided new keys, which may be unrelated to rank in existing_keys
parsed_new_key = ( parsed_new_key [ 0 ] , parsed_new_key [ 1 ] , parsed_options , parsed_new_key [ 3 ] , parsed_new_key [ 4 ] )
if comment is not None :
parsed_new_key = ( parsed_new_key [ 0 ] , parsed_new_key [ 1 ] , parsed_new_key [ 2 ] , comment , parsed_new_key [ 4 ] )
matched = False
non_matching_keys = [ ]
@ -574,7 +594,7 @@ def enforce_state(module, params):
matched = True
# handle idempotent state=present
if state == " present " :
if state == " present " :
keys_to_exist . append ( parsed_new_key [ 0 ] )
if len ( non_matching_keys ) > 0 :
for non_matching_key in non_matching_keys :
@ -590,7 +610,7 @@ def enforce_state(module, params):
existing_keys [ parsed_new_key [ 0 ] ] = ( parsed_new_key [ 0 ] , parsed_new_key [ 1 ] , parsed_new_key [ 2 ] , parsed_new_key [ 3 ] , total_rank )
do_write = True
elif state == " absent " :
elif state == " absent " :
if not matched :
continue
del existing_keys [ parsed_new_key [ 0 ] ]
@ -607,35 +627,41 @@ def enforce_state(module, params):
if do_write :
filename = keyfile ( module , user , do_write , path , manage_dir )
new_content = serialize ( existing_keys )
diff = None
if module . _diff :
diff = {
' before_header ' : params [ ' keyfile ' ] ,
' after_header ' : filename ,
' before ' : existing_content ,
' after ' : new_content ,
}
params [ ' diff ' ] = diff
if module . check_mode :
module . exit_json ( changed = True , diff = diff )
writefile ( module , filename , new_content )
params [ ' changed ' ] = True
params [ ' diff ' ] = diff
else :
if module . check_mode :
module . exit_json ( changed = False )
return params
def main ( ) :
module = AnsibleModule (
argument_spec = dict (
user = dict ( required = True , type = ' str ' ) ,
key = dict ( required = True , type = ' str ' ) ,
path = dict ( required = False , type = ' str ' ) ,
manage_dir = dict ( required = False , type = ' bool ' , default = True ) ,
state = dict ( default = ' present ' , choices = [ ' absent ' , ' present ' ] ) ,
key_options = dict ( required = False , type = ' str ' ) ,
unique = dict ( default = False , type = ' bool ' ) ,
exclusive = dict ( default = False , type = ' bool ' ) ,
validate_certs = dict ( default = True , type = ' bool ' ) ,
argument_spec = dict (
user = dict ( required = True , type = ' str ' ) ,
key = dict ( required = True , type = ' str ' ) ,
path = dict ( required = False , type = ' str ' ) ,
manage_dir = dict ( required = False , type = ' bool ' , default = True ) ,
state = dict ( default = ' present ' , choices = [ ' absent ' , ' present ' ] ) ,
key_options = dict ( required = False , type = ' str ' ) ,
unique = dict ( default = False , type = ' bool ' ) ,
exclusive = dict ( default = False , type = ' bool ' ) ,
comment = dict ( required = False , default = None , type = ' str ' ) ,
validate_certs = dict ( default = True , type = ' bool ' ) ,
) ,
supports_check_mode = True
)
@ -643,5 +669,6 @@ def main():
results = enforce_state ( module , module . params )
module . exit_json ( * * results )
if __name__ == ' __main__ ' :
main ( )