1#!/usr/bin/python 2# -*- coding: utf-8 -*- 3 4# Copyright: Ansible Project 5# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) 6 7from __future__ import absolute_import, division, print_function 8__metaclass__ = type 9 10 11ANSIBLE_METADATA = {'metadata_version': '1.1', 12 'status': ['preview'], 13 'supported_by': 'community'} 14 15 16DOCUMENTATION = ''' 17--- 18module: digital_ocean_sshkey 19short_description: Manage DigitalOcean SSH keys 20description: 21 - Create/delete DigitalOcean SSH keys. 22version_added: "2.4" 23author: "Patrick Marques (@pmarques)" 24options: 25 state: 26 description: 27 - Indicate desired state of the target. 28 default: present 29 choices: ['present', 'absent'] 30 fingerprint: 31 description: 32 - This is a unique identifier for the SSH key used to delete a key 33 version_added: 2.4 34 aliases: ['id'] 35 name: 36 description: 37 - The name for the SSH key 38 ssh_pub_key: 39 description: 40 - The Public SSH key to add. 41 oauth_token: 42 description: 43 - DigitalOcean OAuth token. 44 required: true 45 version_added: 2.4 46notes: 47 - Version 2 of DigitalOcean API is used. 48requirements: 49 - "python >= 2.6" 50''' 51 52 53EXAMPLES = ''' 54- name: "Create ssh key" 55 digital_ocean_sshkey: 56 oauth_token: "{{ oauth_token }}" 57 name: "My SSH Public Key" 58 ssh_pub_key: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAQQDDHr/jh2Jy4yALcK4JyWbVkPRaWmhck3IgCoeOO3z1e2dBowLh64QAM+Qb72pxekALga2oi4GvT+TlWNhzPH4V example" 59 state: present 60 register: result 61 62- name: "Delete ssh key" 63 digital_ocean_sshkey: 64 oauth_token: "{{ oauth_token }}" 65 state: "absent" 66 fingerprint: "3b:16:bf:e4:8b:00:8b:b8:59:8c:a9:d3:f0:19:45:fa" 67''' 68 69 70RETURN = ''' 71# Digital Ocean API info https://developers.digitalocean.com/documentation/v2/#list-all-keys 72data: 73 description: This is only present when C(state=present) 74 returned: when C(state=present) 75 type: dict 76 sample: { 77 "ssh_key": { 78 "id": 512189, 79 "fingerprint": "3b:16:bf:e4:8b:00:8b:b8:59:8c:a9:d3:f0:19:45:fa", 80 "name": "My SSH Public Key", 81 "public_key": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAQQDDHr/jh2Jy4yALcK4JyWbVkPRaWmhck3IgCoeOO3z1e2dBowLh64QAM+Qb72pxekALga2oi4GvT+TlWNhzPH4V example" 82 } 83 } 84''' 85 86import json 87import hashlib 88import base64 89 90from ansible.module_utils.basic import env_fallback 91from ansible.module_utils.basic import AnsibleModule 92from ansible.module_utils.urls import fetch_url 93 94 95class Response(object): 96 97 def __init__(self, resp, info): 98 self.body = None 99 if resp: 100 self.body = resp.read() 101 self.info = info 102 103 @property 104 def json(self): 105 if not self.body: 106 if "body" in self.info: 107 return json.loads(self.info["body"]) 108 return None 109 try: 110 return json.loads(self.body) 111 except ValueError: 112 return None 113 114 @property 115 def status_code(self): 116 return self.info["status"] 117 118 119class Rest(object): 120 121 def __init__(self, module, headers): 122 self.module = module 123 self.headers = headers 124 self.baseurl = 'https://api.digitalocean.com/v2' 125 126 def _url_builder(self, path): 127 if path[0] == '/': 128 path = path[1:] 129 return '%s/%s' % (self.baseurl, path) 130 131 def send(self, method, path, data=None, headers=None): 132 url = self._url_builder(path) 133 data = self.module.jsonify(data) 134 timeout = self.module.params['timeout'] 135 136 resp, info = fetch_url(self.module, url, data=data, headers=self.headers, method=method, timeout=timeout) 137 138 # Exceptions in fetch_url may result in a status -1, the ensures a 139 if info['status'] == -1: 140 self.module.fail_json(msg=info['msg']) 141 142 return Response(resp, info) 143 144 def get(self, path, data=None, headers=None): 145 return self.send('GET', path, data, headers) 146 147 def put(self, path, data=None, headers=None): 148 return self.send('PUT', path, data, headers) 149 150 def post(self, path, data=None, headers=None): 151 return self.send('POST', path, data, headers) 152 153 def delete(self, path, data=None, headers=None): 154 return self.send('DELETE', path, data, headers) 155 156 157def core(module): 158 api_token = module.params['oauth_token'] 159 state = module.params['state'] 160 fingerprint = module.params['fingerprint'] 161 name = module.params['name'] 162 ssh_pub_key = module.params['ssh_pub_key'] 163 164 rest = Rest(module, {'Authorization': 'Bearer {0}'.format(api_token), 165 'Content-type': 'application/json'}) 166 167 fingerprint = fingerprint or ssh_key_fingerprint(ssh_pub_key) 168 response = rest.get('account/keys/{0}'.format(fingerprint)) 169 status_code = response.status_code 170 json = response.json 171 172 if status_code not in (200, 404): 173 module.fail_json(msg='Error getting ssh key [{0}: {1}]'.format( 174 status_code, response.json['message']), fingerprint=fingerprint) 175 176 if state in ('present'): 177 if status_code == 404: 178 # IF key not found create it! 179 180 if module.check_mode: 181 module.exit_json(changed=True) 182 183 payload = { 184 'name': name, 185 'public_key': ssh_pub_key 186 } 187 response = rest.post('account/keys', data=payload) 188 status_code = response.status_code 189 json = response.json 190 if status_code == 201: 191 module.exit_json(changed=True, data=json) 192 193 module.fail_json(msg='Error creating ssh key [{0}: {1}]'.format( 194 status_code, response.json['message'])) 195 196 elif status_code == 200: 197 # If key found was found, check if name needs to be updated 198 if name is None or json['ssh_key']['name'] == name: 199 module.exit_json(changed=False, data=json) 200 201 if module.check_mode: 202 module.exit_json(changed=True) 203 204 payload = { 205 'name': name, 206 } 207 response = rest.put('account/keys/{0}'.format(fingerprint), data=payload) 208 status_code = response.status_code 209 json = response.json 210 if status_code == 200: 211 module.exit_json(changed=True, data=json) 212 213 module.fail_json(msg='Error updating ssh key name [{0}: {1}]'.format( 214 status_code, response.json['message']), fingerprint=fingerprint) 215 216 elif state in ('absent'): 217 if status_code == 404: 218 module.exit_json(changed=False) 219 220 if module.check_mode: 221 module.exit_json(changed=True) 222 223 response = rest.delete('account/keys/{0}'.format(fingerprint)) 224 status_code = response.status_code 225 json = response.json 226 if status_code == 204: 227 module.exit_json(changed=True) 228 229 module.fail_json(msg='Error creating ssh key [{0}: {1}]'.format( 230 status_code, response.json['message'])) 231 232 233def ssh_key_fingerprint(ssh_pub_key): 234 key = ssh_pub_key.split(None, 2)[1] 235 fingerprint = hashlib.md5(base64.b64decode(key)).hexdigest() 236 return ':'.join(a + b for a, b in zip(fingerprint[::2], fingerprint[1::2])) 237 238 239def main(): 240 module = AnsibleModule( 241 argument_spec=dict( 242 state=dict(choices=['present', 'absent'], default='present'), 243 fingerprint=dict(aliases=['id'], required=False), 244 name=dict(required=False), 245 ssh_pub_key=dict(required=False), 246 oauth_token=dict( 247 no_log=True, 248 # Support environment variable for DigitalOcean OAuth Token 249 fallback=(env_fallback, ['DO_API_TOKEN', 'DO_API_KEY', 'DO_OAUTH_TOKEN']), 250 required=True, 251 ), 252 validate_certs=dict(type='bool', default=True), 253 timeout=dict(type='int', default=30), 254 ), 255 required_one_of=( 256 ('fingerprint', 'ssh_pub_key'), 257 ), 258 supports_check_mode=True, 259 ) 260 261 core(module) 262 263 264if __name__ == '__main__': 265 main() 266