mirror of
				https://github.com/ansible-collections/community.general.git
				synced 2024-09-14 20:13:21 +02:00 
			
		
		
		
	
		
			
				
	
	
		
			434 lines
		
	
	
	
		
			14 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			434 lines
		
	
	
	
		
			14 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
#!/usr/bin/python
 | 
						|
# -*- coding: utf-8 -*-
 | 
						|
 | 
						|
# This file is part of Ansible
 | 
						|
#
 | 
						|
# Ansible is free software: you can redistribute it and/or modify
 | 
						|
# it under the terms of the GNU General Public License as published by
 | 
						|
# the Free Software Foundation, either version 3 of the License, or
 | 
						|
# (at your option) any later version.
 | 
						|
#
 | 
						|
# Ansible is distributed in the hope that it will be useful,
 | 
						|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
						|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
						|
# GNU General Public License for more details.
 | 
						|
#
 | 
						|
# You should have received a copy of the GNU General Public License
 | 
						|
# along with Ansible.  If not, see <http://www.gnu.org/licenses/>.
 | 
						|
DOCUMENTATION = '''
 | 
						|
---
 | 
						|
module: digital_ocean
 | 
						|
short_description: Create/delete a droplet/SSH_key in DigitalOcean
 | 
						|
description:
 | 
						|
     - Create/delete a droplet in DigitalOcean and optionally wait for it to be 'running', or deploy an SSH key.
 | 
						|
version_added: "1.3"
 | 
						|
options:
 | 
						|
  command:
 | 
						|
    description:
 | 
						|
     - Which target you want to operate on.
 | 
						|
    default: droplet
 | 
						|
    choices: ['droplet', 'ssh']
 | 
						|
  state:
 | 
						|
    description:
 | 
						|
     - Indicate desired state of the target.
 | 
						|
    default: present
 | 
						|
    choices: ['present', 'active', 'absent', 'deleted']
 | 
						|
  client_id:
 | 
						|
     description:
 | 
						|
     - DigitalOcean manager id.
 | 
						|
  api_key:
 | 
						|
    description:
 | 
						|
     - DigitalOcean api key.
 | 
						|
  id:
 | 
						|
    description:
 | 
						|
     - Numeric, the droplet id you want to operate on.
 | 
						|
  name:
 | 
						|
    description:
 | 
						|
     - String, this is the name of the droplet - must be formatted by hostname rules, or the name of a SSH key.
 | 
						|
  unique_name:
 | 
						|
    description:
 | 
						|
     - Bool, require unique hostnames.  By default, DigitalOcean allows multiple hosts with the same name.  Setting this to "yes" allows only one host per name.  Useful for idempotence.
 | 
						|
    version_added: "1.4"
 | 
						|
    default: "no"
 | 
						|
    choices: [ "yes", "no" ]
 | 
						|
  size_id:
 | 
						|
    description:
 | 
						|
     - Numeric, this is the id of the size you would like the droplet created with.
 | 
						|
  image_id:
 | 
						|
    description:
 | 
						|
     - Numeric, this is the id of the image you would like the droplet created with.
 | 
						|
  region_id:
 | 
						|
    description:
 | 
						|
     - "Numeric, this is the id of the region you would like your server to be created in."
 | 
						|
  ssh_key_ids:
 | 
						|
    description:
 | 
						|
     - Optional, comma separated list of ssh_key_ids that you would like to be added to the server.
 | 
						|
  virtio:
 | 
						|
    description:
 | 
						|
     - "Bool, turn on virtio driver in droplet for improved network and storage I/O."
 | 
						|
    version_added: "1.4"
 | 
						|
    default: "yes"
 | 
						|
    choices: [ "yes", "no" ]
 | 
						|
  private_networking:
 | 
						|
    description:
 | 
						|
     - "Bool, add an additional, private network interface to droplet for inter-droplet communication."
 | 
						|
    version_added: "1.4"
 | 
						|
    default: "no"
 | 
						|
    choices: [ "yes", "no" ]
 | 
						|
  backups_enabled:
 | 
						|
    description:
 | 
						|
     - Optional, Boolean, enables backups for your droplet.
 | 
						|
    version_added: "1.6"
 | 
						|
    default: "no"
 | 
						|
    choices: [ "yes", "no" ]
 | 
						|
  wait:
 | 
						|
    description:
 | 
						|
     - Wait for the droplet to be in state 'running' before returning.  If wait is "no" an ip_address may not be returned.
 | 
						|
    default: "yes"
 | 
						|
    choices: [ "yes", "no" ]
 | 
						|
  wait_timeout:
 | 
						|
    description:
 | 
						|
     - How long before wait gives up, in seconds.
 | 
						|
    default: 300
 | 
						|
  ssh_pub_key:
 | 
						|
    description:
 | 
						|
     - The public SSH key you want to add to your account.
 | 
						|
 | 
						|
notes:
 | 
						|
  - Two environment variables can be used, DO_CLIENT_ID and DO_API_KEY.
 | 
						|
requirements: [ 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
 | 
						|
 | 
						|
- digital_ocean: >
 | 
						|
      state=present
 | 
						|
      command=ssh
 | 
						|
      name=my_ssh_key
 | 
						|
      ssh_pub_key='ssh-rsa AAAA...'
 | 
						|
      client_id=XXX
 | 
						|
      api_key=XXX
 | 
						|
 | 
						|
# Create a new Droplet
 | 
						|
# Will return the droplet details including the droplet id (used for idempotence)
 | 
						|
 | 
						|
- digital_ocean: >
 | 
						|
      state=present
 | 
						|
      command=droplet
 | 
						|
      name=mydroplet
 | 
						|
      client_id=XXX
 | 
						|
      api_key=XXX
 | 
						|
      size_id=1
 | 
						|
      region_id=2
 | 
						|
      image_id=3
 | 
						|
      wait_timeout=500
 | 
						|
  register: my_droplet
 | 
						|
- debug: msg="ID is {{ my_droplet.droplet.id }}"
 | 
						|
- debug: msg="IP is {{ my_droplet.droplet.ip_address }}"
 | 
						|
 | 
						|
# Ensure a droplet is present
 | 
						|
# If droplet id already exist, will return the droplet details and changed = False
 | 
						|
# If no droplet matches the id, a new droplet will be created and the droplet details (including the new id) are returned, changed = True.
 | 
						|
 | 
						|
- digital_ocean: >
 | 
						|
      state=present
 | 
						|
      command=droplet
 | 
						|
      id=123
 | 
						|
      name=mydroplet
 | 
						|
      client_id=XXX
 | 
						|
      api_key=XXX
 | 
						|
      size_id=1
 | 
						|
      region_id=2
 | 
						|
      image_id=3
 | 
						|
      wait_timeout=500
 | 
						|
 | 
						|
# Create a droplet with ssh key
 | 
						|
# The ssh key id can be passed as argument at the creation of a droplet (see ssh_key_ids).
 | 
						|
# Several keys can be added to ssh_key_ids as id1,id2,id3
 | 
						|
# The keys are used to connect as root to the droplet.
 | 
						|
 | 
						|
- digital_ocean: >
 | 
						|
      state=present
 | 
						|
      ssh_key_ids=id1,id2
 | 
						|
      name=mydroplet
 | 
						|
      client_id=XXX
 | 
						|
      api_key=XXX
 | 
						|
      size_id=1
 | 
						|
      region_id=2
 | 
						|
      image_id=3
 | 
						|
'''
 | 
						|
 | 
						|
import sys
 | 
						|
import os
 | 
						|
import time
 | 
						|
 | 
						|
try:
 | 
						|
    import dopy
 | 
						|
    from dopy.manager import DoError, DoManager
 | 
						|
except ImportError, e:
 | 
						|
    print "failed=True msg='dopy >= 0.2.3 required for this module'"
 | 
						|
    sys.exit(1)
 | 
						|
 | 
						|
if dopy.__version__ < '0.2.3':
 | 
						|
    print "failed=True msg='dopy >= 0.2.3 required for this module'"
 | 
						|
    sys.exit(1)
 | 
						|
 | 
						|
class TimeoutError(DoError):
 | 
						|
    def __init__(self, msg, id):
 | 
						|
        super(TimeoutError, self).__init__(msg)
 | 
						|
        self.id = id
 | 
						|
 | 
						|
class JsonfyMixIn(object):
 | 
						|
    def to_json(self):
 | 
						|
        return self.__dict__
 | 
						|
 | 
						|
class Droplet(JsonfyMixIn):
 | 
						|
    manager = None
 | 
						|
 | 
						|
    def __init__(self, droplet_json):
 | 
						|
        self.status = 'new'
 | 
						|
        self.__dict__.update(droplet_json)
 | 
						|
 | 
						|
    def is_powered_on(self):
 | 
						|
        return self.status == 'active'
 | 
						|
 | 
						|
    def update_attr(self, attrs=None):
 | 
						|
        if attrs:
 | 
						|
            for k, v in attrs.iteritems():
 | 
						|
                setattr(self, k, v)
 | 
						|
        else:
 | 
						|
            json = self.manager.show_droplet(self.id)
 | 
						|
            if json['ip_address']:
 | 
						|
                self.update_attr(json)
 | 
						|
 | 
						|
    def power_on(self):
 | 
						|
        assert self.status == 'off', 'Can only power on a closed one.'
 | 
						|
        json = self.manager.power_on_droplet(self.id)
 | 
						|
        self.update_attr(json)
 | 
						|
 | 
						|
    def ensure_powered_on(self, wait=True, wait_timeout=300):
 | 
						|
        if self.is_powered_on():
 | 
						|
            return
 | 
						|
        if self.status == 'off':  # powered off
 | 
						|
            self.power_on()
 | 
						|
 | 
						|
        if wait:
 | 
						|
            end_time = time.time() + wait_timeout
 | 
						|
            while time.time() < end_time:
 | 
						|
                time.sleep(min(20, end_time - time.time()))
 | 
						|
                self.update_attr()
 | 
						|
                if self.is_powered_on():
 | 
						|
                    if not self.ip_address:
 | 
						|
                        raise TimeoutError('No ip is found.', self.id)
 | 
						|
                    return
 | 
						|
            raise TimeoutError('Wait for droplet running timeout', self.id)
 | 
						|
 | 
						|
    def destroy(self):
 | 
						|
        return self.manager.destroy_droplet(self.id, scrub_data=True)
 | 
						|
 | 
						|
    @classmethod
 | 
						|
    def setup(cls, client_id, api_key):
 | 
						|
        cls.manager = DoManager(client_id, api_key)
 | 
						|
 | 
						|
    @classmethod
 | 
						|
    def add(cls, name, size_id, image_id, region_id, ssh_key_ids=None, virtio=True, private_networking=False, backups_enabled=False):
 | 
						|
        json = cls.manager.new_droplet(name, size_id, image_id, region_id, ssh_key_ids, virtio, private_networking, backups_enabled)
 | 
						|
        droplet = cls(json)
 | 
						|
        return droplet
 | 
						|
 | 
						|
    @classmethod
 | 
						|
    def find(cls, id=None, name=None):
 | 
						|
        if not id and not name:
 | 
						|
            return False
 | 
						|
 | 
						|
        droplets = cls.list_all()
 | 
						|
 | 
						|
        # Check first by id.  digital ocean requires that it be unique
 | 
						|
        for droplet in droplets:
 | 
						|
            if droplet.id == id:
 | 
						|
                return droplet
 | 
						|
 | 
						|
        # Failing that, check by hostname.
 | 
						|
        for droplet in droplets:
 | 
						|
            if droplet.name == name:
 | 
						|
                return droplet
 | 
						|
 | 
						|
        return False
 | 
						|
 | 
						|
    @classmethod
 | 
						|
    def list_all(cls):
 | 
						|
        json = cls.manager.all_active_droplets()
 | 
						|
        return map(cls, json)
 | 
						|
 | 
						|
class SSH(JsonfyMixIn):
 | 
						|
    manager = None
 | 
						|
 | 
						|
    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
 | 
						|
 | 
						|
    @classmethod
 | 
						|
    def setup(cls, client_id, api_key):
 | 
						|
        cls.manager = DoManager(client_id, api_key)
 | 
						|
 | 
						|
    @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
 | 
						|
 | 
						|
    @classmethod
 | 
						|
    def list_all(cls):
 | 
						|
        json = cls.manager.all_ssh_keys()
 | 
						|
        return map(cls, json)
 | 
						|
 | 
						|
    @classmethod
 | 
						|
    def add(cls, name, key_pub):
 | 
						|
        json = cls.manager.new_ssh_key(name, key_pub)
 | 
						|
        return cls(json)
 | 
						|
 | 
						|
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, e:
 | 
						|
        module.fail_json(msg='Unable to load %s' % e.message)
 | 
						|
 | 
						|
    changed = True
 | 
						|
    command = module.params['command']
 | 
						|
    state = module.params['state']
 | 
						|
 | 
						|
    if command == 'droplet':
 | 
						|
        Droplet.setup(client_id, api_key)
 | 
						|
        if state in ('active', 'present'):
 | 
						|
 | 
						|
            # First, try to find a droplet by id.
 | 
						|
            droplet = Droplet.find(id=module.params['id'])
 | 
						|
 | 
						|
            # If we couldn't find the droplet and the user is allowing unique
 | 
						|
            # hostnames, then check to see if a droplet with the specified
 | 
						|
            # hostname already exists.
 | 
						|
            if not droplet and module.params['unique_name']:
 | 
						|
                droplet = Droplet.find(name=getkeyordie('name'))
 | 
						|
 | 
						|
            # If both of those attempts failed, then create a new droplet.
 | 
						|
            if not droplet:
 | 
						|
                droplet = Droplet.add(
 | 
						|
                    name=getkeyordie('name'),
 | 
						|
                    size_id=getkeyordie('size_id'),
 | 
						|
                    image_id=getkeyordie('image_id'),
 | 
						|
                    region_id=getkeyordie('region_id'),
 | 
						|
                    ssh_key_ids=module.params['ssh_key_ids'],
 | 
						|
                    virtio=module.params['virtio'],
 | 
						|
                    private_networking=module.params['private_networking'],
 | 
						|
                    backups_enabled=module.params['backups_enabled'],
 | 
						|
                )
 | 
						|
 | 
						|
            if droplet.is_powered_on():
 | 
						|
                changed = False
 | 
						|
 | 
						|
            droplet.ensure_powered_on(
 | 
						|
                wait=getkeyordie('wait'),
 | 
						|
                wait_timeout=getkeyordie('wait_timeout')
 | 
						|
            )
 | 
						|
 | 
						|
            module.exit_json(changed=changed, droplet=droplet.to_json())
 | 
						|
 | 
						|
        elif state in ('absent', 'deleted'):
 | 
						|
            # First, try to find a droplet by id.
 | 
						|
            droplet = Droplet.find(module.params['id'])
 | 
						|
 | 
						|
            # If we couldn't find the droplet and the user is allowing unique
 | 
						|
            # hostnames, then check to see if a droplet with the specified
 | 
						|
            # hostname already exists.
 | 
						|
            if not droplet and module.params['unique_name']:
 | 
						|
                droplet = Droplet.find(name=getkeyordie('name'))
 | 
						|
 | 
						|
            if not droplet:
 | 
						|
                module.exit_json(changed=False, msg='The droplet is not found.')
 | 
						|
 | 
						|
            event_json = droplet.destroy()
 | 
						|
            module.exit_json(changed=True, event_id=event_json['event_id'])
 | 
						|
 | 
						|
    elif command == 'ssh':
 | 
						|
        SSH.setup(client_id, api_key)
 | 
						|
        name = getkeyordie('name')
 | 
						|
        if state in ('active', '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())
 | 
						|
 | 
						|
        elif state in ('absent', 'deleted'):
 | 
						|
            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()
 | 
						|
            module.exit_json(changed=True)
 | 
						|
 | 
						|
 | 
						|
def main():
 | 
						|
    module = AnsibleModule(
 | 
						|
        argument_spec = dict(
 | 
						|
            command = dict(choices=['droplet', 'ssh'], default='droplet'),
 | 
						|
            state = dict(choices=['active', 'present', 'absent', 'deleted'], default='present'),
 | 
						|
            client_id = dict(aliases=['CLIENT_ID'], no_log=True),
 | 
						|
            api_key = dict(aliases=['API_KEY'], no_log=True),
 | 
						|
            name = dict(type='str'),
 | 
						|
            size_id = dict(type='int'),
 | 
						|
            image_id = dict(type='int'),
 | 
						|
            region_id = dict(type='int'),
 | 
						|
            ssh_key_ids = dict(default=''),
 | 
						|
            virtio = dict(type='bool', default='yes'),
 | 
						|
            private_networking = dict(type='bool', default='no'),
 | 
						|
            backups_enabled = dict(type='bool', default='no'),
 | 
						|
            id = dict(aliases=['droplet_id'], type='int'),
 | 
						|
            unique_name = dict(type='bool', default='no'),
 | 
						|
            wait = dict(type='bool', default=True),
 | 
						|
            wait_timeout = dict(default=300, type='int'),
 | 
						|
            ssh_pub_key = dict(type='str'),
 | 
						|
        ),
 | 
						|
        required_together = (
 | 
						|
            ['size_id', 'image_id', 'region_id'],
 | 
						|
        ),
 | 
						|
        mutually_exclusive = (
 | 
						|
            ['size_id', 'ssh_pub_key'],
 | 
						|
            ['image_id', 'ssh_pub_key'],
 | 
						|
            ['region_id', 'ssh_pub_key'],
 | 
						|
        ),
 | 
						|
        required_one_of = (
 | 
						|
            ['id', 'name'],
 | 
						|
        ),
 | 
						|
    )
 | 
						|
 | 
						|
    try:
 | 
						|
        core(module)
 | 
						|
    except TimeoutError, e:
 | 
						|
        module.fail_json(msg=str(e), id=e.id)
 | 
						|
    except (DoError, Exception), e:
 | 
						|
        module.fail_json(msg=str(e))
 | 
						|
 | 
						|
# import module snippets
 | 
						|
from ansible.module_utils.basic import *
 | 
						|
 | 
						|
main()
 |