Update module digital_ocean_sshkey (#18930)

* add new module do_sshkeys

* Deprecate digital_ocean_sshkey.py in favor of do_sshkeys

* Should not exit changed if name not set and key exist

* Add ansible metadata

* Return "ansible_facts" instead of "data" key

* Update documentation and remove unused import

* Remove facts module (1 module / PR)

* Fix

* Fix

* fix version_added

* Change old module status to deprecated

* Change old module status to deprecated

* Fix module deprecation

* Add support for new DO_OAUTH_TOKEN env var

* Fix python 2.6 positional index

* Update deprecation info

* Configure timeout and validate_certs for fetch_url

* rename do_sshkeys to digital_ocean_sshkeys

* Module is replaced, not deprecated anymore

* Fix module name

* Add version to new parameters

* Update module from boilerplate
pull/27840/merge
Patrick Marques 7 years ago committed by Ryan Brown
parent ac15df8ab9
commit f1816bb438

@ -16,164 +16,253 @@ ANSIBLE_METADATA = {'metadata_version': '1.0',
DOCUMENTATION = ''' DOCUMENTATION = '''
--- ---
module: digital_ocean_sshkey module: digital_ocean_sshkey
short_description: Create/delete an SSH key in DigitalOcean short_description: Manage DigitalOcean SSH keys
description: description:
- Create/delete an SSH key. - Create/delete DigitalOcean SSH keys.
version_added: "1.6" version_added: "2.4"
author: "Michael Gregson (@mgregson)" author: "Patrick Marques (@pmarques)"
options: options:
state: state:
description: description:
- Indicate desired state of the target. - Indicate desired state of the target.
default: present default: present
choices: ['present', 'absent'] choices: ['present', 'absent']
client_id: fingerprint:
description:
- DigitalOcean manager id.
api_key:
description: description:
- DigitalOcean api key. - This is a unique identifier for the SSH key used to delete a key
id: required: false
description: default: None
- Numeric, the SSH key id you want to operate on. version_added: 2.4
name: name:
description: 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: ssh_pub_key:
description: 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: notes:
- Two environment variables can be used, DO_CLIENT_ID and DO_API_KEY. - Version 2 of DigitalOcean API is used.
- Version 1 of DigitalOcean API is used.
requirements: requirements:
- "python >= 2.6" - "python >= 2.6"
- dopy
''' '''
EXAMPLES = ''' EXAMPLES = '''
# Ensure a SSH key is present - name: "Create ssh key"
# If a key matches this name, will return the ssh key id and changed = False digital_ocean_sshkey:
# If no existing key matches this name, a new key is created, the ssh key id is returned and changed = False 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 os
import traceback import hashlib
import base64
try:
from dopy.manager import DoError, DoManager
HAS_DOPY = True
except ImportError:
HAS_DOPY = False
from ansible.module_utils.basic import env_fallback
from ansible.module_utils.basic import AnsibleModule 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): def __init__(self, resp, info):
return self.__dict__ 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): @property
manager = None 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): class Rest(object):
self.manager.destroy_ssh_key(self.id)
return True
@classmethod def __init__(self, module, headers):
def setup(cls, client_id, api_key): self.module = module
cls.manager = DoManager(client_id, api_key) self.headers = headers
self.baseurl = 'https://api.digitalocean.com/v2'
@classmethod def _url_builder(self, path):
def find(cls, name): if path[0] == '/':
if not name: path = path[1:]
return False return '%s/%s' % (self.baseurl, path)
keys = cls.list_all()
for key in keys:
if key.name == name:
return key
return False
@classmethod def send(self, method, path, data=None, headers=None):
def list_all(cls): url = self._url_builder(path)
json = cls.manager.all_ssh_keys() data = self.module.jsonify(data)
return map(cls, json) timeout = self.module.params['timeout']
@classmethod resp, info = fetch_url(self.module, url, data=data, headers=self.headers, method=method, timeout=timeout)
def add(cls, name, key_pub):
json = cls.manager.new_ssh_key(name, key_pub)
return cls(json)
# Exceptions in fetch_url may result in a status -1, the ensures a
if info['status'] == -1:
self.module.fail_json(msg=info['msg'])
return Response(resp, info)
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):
def getkeyordie(k):
v = module.params[k]
if v is None:
module.fail_json(msg='Unable to load %s' % k)
return v
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 core(module):
api_token = module.params['oauth_token']
state = module.params['state'] 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'): if state in ('present'):
key = SSH.find(name) if status_code == 404:
if key: # IF key not found create it!
module.exit_json(changed=False, ssh_key=key.to_json())
key = SSH.add(name, getkeyordie('ssh_pub_key')) if module.check_mode:
module.exit_json(changed=True, ssh_key=key.to_json()) 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'): elif state in ('absent'):
key = SSH.find(name) if status_code == 404:
if not key: module.exit_json(changed=False)
module.exit_json(changed=False, msg='SSH key with the name of %s is not found.' % name)
key.destroy() if module.check_mode:
module.exit_json(changed=True) 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(): def main():
module = AnsibleModule( module = AnsibleModule(
argument_spec=dict( argument_spec=dict(
state=dict(choices=['present', 'absent'], default='present'), state=dict(choices=['present', 'absent'], default='present'),
client_id=dict(aliases=['CLIENT_ID'], no_log=True), fingerprint=dict(aliases=['id'], required=False),
api_key=dict(aliases=['API_KEY'], no_log=True), name=dict(required=False),
name=dict(type='str'), ssh_pub_key=dict(required=False),
id=dict(aliases=['droplet_id'], type='int'), oauth_token=dict(
ssh_pub_key=dict(type='str'), 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=( 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)
core(module)
except (DoError, Exception) as e:
module.fail_json(msg=str(e), exception=traceback.format_exc())
if __name__ == '__main__': if __name__ == '__main__':

Loading…
Cancel
Save