@ -16,164 +16,253 @@ ANSIBLE_METADATA = {'metadata_version': '1.0',
DOCUMENTATION = '''
- - -
module : digital_ocean_sshkey
short_description : Create/ delete an SSH key in DigitalOcean
short_description : Manage DigitalOcean SSH keys
description :
- Create / delete an SSH key .
version_added : " 1.6 "
author : " Michael Gregson (@mgregson )"
- Create / delete DigitalOce an SSH key s .
version_added : " 2.4 "
author : " Patrick Marques (@pmarques )"
options :
state :
description :
- Indicate desired state of the target .
default : present
choices : [ ' present ' , ' absent ' ]
client_id :
fingerprint :
description :
- DigitalOcean manager id .
api_key :
description :
- DigitalOcean api key .
id :
description :
- Numeric , the SSH key id you want to operate on .
- This is a unique identifier for the SSH key used to delete a key
required : false
default : None
version_added : 2.4
name :
description :
- String , this is the name of an SSH key to create or destroy .
- The name for the SSH key
required : false
default : None
ssh_pub_key :
description :
- The public SSH key you want to add to your account .
- The Public SSH key to add .
required : false
default : None
oauth_token :
description :
- DigitalOcean OAuth token .
required : true
version_added : 2.4
notes :
- Two environment variables can be used , DO_CLIENT_ID and DO_API_KEY .
- Version 1 of DigitalOcean API is used .
- Version 2 of DigitalOcean API is used .
requirements :
- " python >= 2.6 "
- dopy
'''
EXAMPLES = '''
# Ensure a SSH key is present
# If a key matches this name, will return the ssh key id and changed = False
# If no existing key matches this name, a new key is created, the ssh key id is returned and changed = False
- name : " Create ssh key "
digital_ocean_sshkey :
name : " My SSH Public Key "
public_key : " ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAQQDDHr/jh2Jy4yALcK4JyWbVkPRaWmhck3IgCoeOO3z1e2dBowLh64QAM+Qb72pxekALga2oi4GvT+TlWNhzPH4V example "
register : result
- name : " Delete ssh key "
digital_ocean_sshkey :
state : " absent "
fingerprint : " 3b:16:bf:e4:8b:00:8b:b8:59:8c:a9:d3:f0:19:45:fa "
'''
- digital_ocean_sshkey :
state : present
name : my_ssh_key
ssh_pub_key : ' ssh-rsa AAAA... '
client_id : XXX
api_key : XXX
RETURN = '''
# Digital Ocean API info https://developers.digitalocean.com/documentation/v2/#list-all-keys
data :
description : This is only present when C ( state = present )
returned : when C ( state = present )
type : dict
sample : {
" ssh_key " : {
" id " : 512189 ,
" fingerprint " : " 3b:16:bf:e4:8b:00:8b:b8:59:8c:a9:d3:f0:19:45:fa " ,
" name " : " My SSH Public Key " ,
" public_key " : " ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAQQDDHr/jh2Jy4yALcK4JyWbVkPRaWmhck3IgCoeOO3z1e2dBowLh64QAM+Qb72pxekALga2oi4GvT+TlWNhzPH4V example "
}
}
'''
import json
import os
import traceback
try :
from dopy . manager import DoError , DoManager
HAS_DOPY = True
except ImportError :
HAS_DOPY = False
import hashlib
import base64
from ansible . module_utils . basic import env_fallback
from ansible . module_utils . basic import AnsibleModule
from ansible . module_utils . urls import fetch_url
class JsonfyMixIn ( object ) :
class Response ( object ) :
def to_json ( self ) :
return self . __dict__
def __init__ ( self , resp , info ) :
self . body = None
if resp :
self . body = resp . read ( )
self . info = info
@property
def json ( self ) :
if not self . body :
if " body " in self . info :
return json . loads ( self . info [ " body " ] )
return None
try :
return json . loads ( self . body )
except ValueError :
return None
class SSH ( JsonfyMixIn ) :
manager = None
@property
def status_code ( self ) :
return self . info [ " status " ]
def __init__ ( self , ssh_key_json ) :
self . __dict__ . update ( ssh_key_json )
update_attr = __init__
def destroy ( self ) :
self . manager . destroy_ssh_key ( self . id )
return True
class Rest ( object ) :
@classmethod
def setup ( cls , client_id , api_key ) :
cls . manager = DoManager ( client_id , api_key )
def __init__ ( self , module , headers ) :
self . module = module
self . headers = headers
self . baseurl = ' https://api.digitalocean.com/v2 '
@classmethod
def find ( cls , name ) :
if not name :
return False
keys = cls . list_all ( )
for key in keys :
if key . name == name :
return key
return False
def _url_builder ( self , path ) :
if path [ 0 ] == ' / ' :
path = path [ 1 : ]
return ' %s / %s ' % ( self . baseurl , path )
@classmethod
def list_all ( cls ) :
json = cls . manager . all_ssh_keys ( )
return map ( cls , json )
def send ( self , method , path , data = None , headers = None ) :
url = self . _url_builder ( path )
data = self . module . jsonify ( data )
timeout = self . module . params [ ' timeout ' ]
@classmethod
def add ( cls , name , key_pub ) :
json = cls . manager . new_ssh_key ( name , key_pub )
return cls ( json )
resp , info = fetch_url ( self . module , url , data = data , headers = self . headers , method = method , timeout = timeout )
# Exceptions in fetch_url may result in a status -1, the ensures a
if info [ ' status ' ] == - 1 :
self . module . fail_json ( msg = info [ ' msg ' ] )
def core ( module ) :
def getkeyordie ( k ) :
v = module . params [ k ]
if v is None :
module . fail_json ( msg = ' Unable to load %s ' % k )
return v
return Response ( resp , info )
try :
# params['client_id'] will be None even if client_id is not passed in
client_id = module . params [ ' client_id ' ] or os . environ [ ' DO_CLIENT_ID ' ]
api_key = module . params [ ' api_key ' ] or os . environ [ ' DO_API_KEY ' ]
except KeyError as e :
module . fail_json ( msg = ' Unable to load %s ' % e . message )
def get ( self , path , data = None , headers = None ) :
return self . send ( ' GET ' , path , data , headers )
def put ( self , path , data = None , headers = None ) :
return self . send ( ' PUT ' , path , data , headers )
def post ( self , path , data = None , headers = None ) :
return self . send ( ' POST ' , path , data , headers )
def delete ( self , path , data = None , headers = None ) :
return self . send ( ' DELETE ' , path , data , headers )
def core ( module ) :
api_token = module . params [ ' oauth_token ' ]
state = module . params [ ' state ' ]
fingerprint = module . params [ ' fingerprint ' ]
name = module . params [ ' name ' ]
ssh_pub_key = module . params [ ' ssh_pub_key ' ]
rest = Rest ( module , { ' Authorization ' : ' Bearer {0} ' . format ( api_token ) ,
' Content-type ' : ' application/json ' } )
fingerprint = fingerprint or ssh_key_fingerprint ( ssh_pub_key )
response = rest . get ( ' account/keys/ {0} ' . format ( fingerprint ) )
status_code = response . status_code
json = response . json
if status_code not in ( 200 , 404 ) :
module . fail_json ( msg = ' Error getting ssh key [ {0} : {1} ] ' . format (
status_code , response . json [ ' message ' ] ) , fingerprint = fingerprint )
SSH . setup ( client_id , api_key )
name = getkeyordie ( ' name ' )
if state in ( ' present ' ) :
key = SSH . find ( name )
if key :
module . exit_json ( changed = False , ssh_key = key . to_json ( ) )
key = SSH . add ( name , getkeyordie ( ' ssh_pub_key ' ) )
module . exit_json ( changed = True , ssh_key = key . to_json ( ) )
if status_code == 404 :
# IF key not found create it!
if module . check_mode :
module . exit_json ( changed = True )
payload = {
' name ' : name ,
' public_key ' : ssh_pub_key
}
response = rest . post ( ' account/keys ' , data = payload )
status_code = response . status_code
json = response . json
if status_code == 201 :
module . exit_json ( changed = True , data = json )
module . fail_json ( msg = ' Error creating ssh key [ {0} : {1} ] ' . format (
status_code , response . json [ ' message ' ] ) )
elif status_code == 200 :
# If key found was found, check if name needs to be updated
if name is None or json [ ' ssh_key ' ] [ ' name ' ] == name :
module . exit_json ( changed = False , data = json )
if module . check_mode :
module . exit_json ( changed = True )
payload = {
' name ' : name ,
}
response = rest . put ( ' account/keys/ {0} ' . format ( fingerprint ) , data = payload )
status_code = response . status_code
json = response . json
if status_code == 200 :
module . exit_json ( changed = True , data = json )
module . fail_json ( msg = ' Error updating ssh key name [ {0} : {1} ] ' . format (
status_code , response . json [ ' message ' ] ) , fingerprint = fingerprint )
elif state in ( ' absent ' ) :
key = SSH . find ( name )
if not key :
module . exit_json ( changed = False , msg = ' SSH key with the name of %s is not found. ' % name )
key . destroy ( )
if status_code == 404 :
module . exit_json ( changed = False )
if module . check_mode :
module . exit_json ( changed = True )
response = rest . delete ( ' account/keys/ {0} ' . format ( fingerprint ) )
status_code = response . status_code
json = response . json
if status_code == 204 :
module . exit_json ( changed = True )
module . fail_json ( msg = ' Error creating ssh key [ {0} : {1} ] ' . format (
status_code , response . json [ ' message ' ] ) )
def ssh_key_fingerprint ( ssh_pub_key ) :
key = ssh_pub_key . split ( None , 2 ) [ 1 ]
fingerprint = hashlib . md5 ( base64 . decodestring ( key ) ) . hexdigest ( )
return ' : ' . join ( a + b for a , b in zip ( fingerprint [ : : 2 ] , fingerprint [ 1 : : 2 ] ) )
def main ( ) :
module = AnsibleModule (
argument_spec = dict (
state = dict ( choices = [ ' present ' , ' absent ' ] , default = ' present ' ) ,
client_id = dict ( aliases = [ ' CLIENT_ID ' ] , no_log = True ) ,
api_key = dict ( aliases = [ ' API_KEY ' ] , no_log = True ) ,
name = dict ( type = ' str ' ) ,
id = dict ( aliases = [ ' droplet_id ' ] , type = ' int ' ) ,
ssh_pub_key = dict ( type = ' str ' ) ,
fingerprint = dict ( aliases = [ ' id ' ] , required = False ) ,
name = dict ( required = False ) ,
ssh_pub_key = dict ( required = False ) ,
oauth_token = dict (
no_log = True ,
# Support environment variable for DigitalOcean OAuth Token
fallback = ( env_fallback , [ ' DO_API_TOKEN ' , ' DO_API_KEY ' , ' DO_OAUTH_TOKEN ' ] ) ,
required = True ,
) ,
validate_certs = dict ( type = ' bool ' , default = True ) ,
timeout = dict ( type = ' int ' , default = 30 ) ,
) ,
required_one_of = (
[ ' id ' , ' name ' ] ,
(' fingerprint ' , ' ssh_pub_key ' ) ,
) ,
supports_check_mode = True ,
)
if not HAS_DOPY :
module . fail_json ( msg = ' dopy required for this module ' )
try :
core ( module )
except ( DoError , Exception ) as e :
module . fail_json ( msg = str ( e ) , exception = traceback . format_exc ( ) )
if __name__ == ' __main__ ' :