From 57497a490b116dc1e0bba133ecfeb6c0ed05774d Mon Sep 17 00:00:00 2001 From: Yanis Guenane Date: Fri, 17 Aug 2018 10:30:57 +0200 Subject: [PATCH] Vultr: Introducing vultrr_block_storage module (#43202) This commit introduces a new module called vultr_block_storage/ It allows a user to manage block storage volumes on the Vultr cloud. --- lib/ansible/module_utils/vultr.py | 10 + .../cloud/vultr/vultr_block_storage.py | 240 ++++++++++++++++++ .../vultr_block_storage/defaults/main.yml | 4 + .../roles/vultr_block_storage/tasks/main.yml | 111 ++++++++ test/legacy/vultr.yml | 1 + 5 files changed, 366 insertions(+) create mode 100644 lib/ansible/modules/cloud/vultr/vultr_block_storage.py create mode 100644 test/legacy/roles/vultr_block_storage/defaults/main.yml create mode 100644 test/legacy/roles/vultr_block_storage/tasks/main.yml diff --git a/lib/ansible/module_utils/vultr.py b/lib/ansible/module_utils/vultr.py index 3885bcfb00b..dc9b9c124a9 100644 --- a/lib/ansible/module_utils/vultr.py +++ b/lib/ansible/module_utils/vultr.py @@ -299,3 +299,13 @@ class Vultr: resource='app', use_cache=True ) + + def get_region(self, region=None, key='name'): + value = region or self.module.params.get('region') + + return self.query_resource_by_key( + key=key, + value=value, + resource='regions', + use_cache=True + ) diff --git a/lib/ansible/modules/cloud/vultr/vultr_block_storage.py b/lib/ansible/modules/cloud/vultr/vultr_block_storage.py new file mode 100644 index 00000000000..c383dde8197 --- /dev/null +++ b/lib/ansible/modules/cloud/vultr/vultr_block_storage.py @@ -0,0 +1,240 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# (c) 2018, Yanis Guenane +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +ANSIBLE_METADATA = {'metadata_version': '1.1', + 'status': ['preview'], + 'supported_by': 'community'} + +DOCUMENTATION = ''' +--- +module: vultr_block_storage +short_description: Manages block storage volumes on Vultr. +description: + - Manage block storage volumes on Vultr. +version_added: "2.7" +author: "Yanis Guenane (@Spredzy)" +options: + name: + description: + - Name of the block storage volume. + required: true + aliases: [ description, label ] + size: + description: + - Size of the block storage volume in GB. + required: true + region: + description: + - Region the block storage volume is deployed into. + required: true + state: + description: + - State of the block storage volume. + default: present + choices: [ present, absent ] +extends_documentation_fragment: vultr +''' + +EXAMPLES = ''' +- name: Ensure a block storage volume is present + local_action: + module: vultr_block_storage + name: myvolume + size: 10 + region: Amsterdam + +- name: Ensure a block storage volume is absent + local_action: + module: vultr_block_storage + name: myvolume + state: absent +''' + +RETURN = ''' +--- +vultr_api: + description: Response from Vultr API with a few additions/modification + returned: success + type: complex + contains: + api_account: + description: Account used in the ini file to select the key + returned: success + type: string + sample: default + api_timeout: + description: Timeout used for the API requests + returned: success + type: int + sample: 60 + api_retries: + description: Amount of max retries for the API requests + returned: success + type: int + sample: 5 + api_endpoint: + description: Endpoint used for the API requests + returned: success + type: string + sample: "https://api.vultr.com" +vultr_block_storage: + description: Response from Vultr API + returned: success + type: complex + contains: + attached_to_id: + description: The ID of the server the volume is attached to + returned: success + type: string + sample: "10194376" + cost_per_month: + description: Cost per month for the volume + returned: success + type: float + sample: 1.00 + date_created: + description: Date when the volume was created + returned: success + type: string + sample: "2017-08-26 12:47:48" + id: + description: ID of the block storage volume + returned: success + type: string + sample: "1234abcd" + name: + description: Name of the volume + returned: success + type: string + sample: "ansible-test-volume" + region: + description: Region the volume was deployed into + returned: success + type: string + sample: "New Jersey" + size: + description: Information about the volume size in GB + returned: success + type: int + sample: 10 + status: + description: Status about the deployment of the volume + returned: success + type: string + sample: "active" + +''' + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.vultr import ( + Vultr, + vultr_argument_spec, +) + + +class AnsibleVultrBlockStorage(Vultr): + + def __init__(self, module): + super(AnsibleVultrBlockStorage, self).__init__(module, "vultr_block_storage") + + self.returns = { + 'SUBID': dict(key='id'), + 'label': dict(key='name'), + 'DCID': dict(key='region', transform=self._get_region_name), + 'attached_to_SUBID': dict(key='attached_to_id'), + 'cost_per_month': dict(convert_to='float'), + 'date_created': dict(), + 'size_gb': dict(key='size', convert_to='int'), + 'status': dict() + } + + def _get_region_name(self, region): + return self.get_region(region, 'DCID').get('name') + + def get_block_storage_volumes(self): + volumes = self.api_query(path="/v1/block/list") + if volumes: + for volume in volumes: + if volume.get('label') == self.module.params.get('name'): + return volume + return {} + + def present_block_storage_volume(self): + volume = self.get_block_storage_volumes() + if not volume: + volume = self._create_block_storage_volume(volume) + return volume + + def _create_block_storage_volume(self, volume): + self.result['changed'] = True + data = { + 'label': self.module.params.get('name'), + 'DCID': self.get_region().get('DCID'), + 'size_gb': self.module.params.get('size') + } + self.result['diff']['before'] = {} + self.result['diff']['after'] = data + + if not self.module.check_mode: + self.api_query( + path="/v1/block/create", + method="POST", + data=data + ) + volume = self.get_block_storage_volumes() + return volume + + def absent_block_storage_volume(self): + volume = self.get_block_storage_volumes() + if volume: + self.result['changed'] = True + + data = { + 'SUBID': volume['SUBID'], + } + + self.result['diff']['before'] = volume + self.result['diff']['after'] = {} + + if not self.module.check_mode: + self.api_query( + path="/v1/block/delete", + method="POST", + data=data + ) + return volume + + +def main(): + argument_spec = vultr_argument_spec() + argument_spec.update(dict( + name=dict(required=True, aliases=['description', 'label']), + size=dict(type='int'), + region=dict(), + state=dict(choices=['present', 'absent'], default='present'), + )) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[['state', 'present', ['size', 'region']]] + ) + + vultr_block_storage = AnsibleVultrBlockStorage(module) + if module.params.get('state') == "absent": + volume = vultr_block_storage.absent_block_storage_volume() + else: + volume = vultr_block_storage.present_block_storage_volume() + + result = vultr_block_storage.get_result(volume) + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/test/legacy/roles/vultr_block_storage/defaults/main.yml b/test/legacy/roles/vultr_block_storage/defaults/main.yml new file mode 100644 index 00000000000..1eab91c4d11 --- /dev/null +++ b/test/legacy/roles/vultr_block_storage/defaults/main.yml @@ -0,0 +1,4 @@ +--- +vultr_block_storage_name: ansible-test-vm1 +vultr_block_storage_size: 10 +vultr_block_storage_region: New Jersey diff --git a/test/legacy/roles/vultr_block_storage/tasks/main.yml b/test/legacy/roles/vultr_block_storage/tasks/main.yml new file mode 100644 index 00000000000..3ef16fa775f --- /dev/null +++ b/test/legacy/roles/vultr_block_storage/tasks/main.yml @@ -0,0 +1,111 @@ +# Copyright (c) 2018, Yanis Guenane +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +--- +- name: setup + vultr_block_storage: + name: "{{ vultr_block_storage_name }}" + state: absent + register: result +- name: verify setup + assert: + that: + - result is success + +- name: test fail if missing name + vultr_block_storage: + register: result + ignore_errors: yes +- name: verify test fail if missing name + assert: + that: + - result is failed + - 'result.msg == "missing required arguments: name"' + +- name: test fail if missing params for state=present + vultr_block_storage: + name: "{{ vultr_block_storage_name }}" + register: result + ignore_errors: yes +- name: verify fail if missing params for state=present + assert: + that: + - result is failed + - 'result.msg == "state is present but all of the following are missing: size, region"' + +- name: test create block storage volume in check mode + vultr_block_storage: + name: "{{ vultr_block_storage_name }}" + size: "{{ vultr_block_storage_size }}" + region: "{{ vultr_block_storage_region }}" + register: result + check_mode: yes +- name: verify test create server in check mode + assert: + that: + - result is changed + +- name: test create block storage volume + vultr_block_storage: + name: "{{ vultr_block_storage_name }}" + size: "{{ vultr_block_storage_size }}" + region: "{{ vultr_block_storage_region }}" + register: result + +- name: verify test create block storage volume + assert: + that: + - result is changed + - result.vultr_block_storage.name == "{{ vultr_block_storage_name }}" + - result.vultr_block_storage.region == "{{ vultr_block_storage_region }}" + - result.vultr_block_storage.size == 10 + +- name: test create block storage volume idempotence + vultr_block_storage: + name: "{{ vultr_block_storage_name }}" + size: "{{ vultr_block_storage_size }}" + region: "{{ vultr_block_storage_region }}" + register: result + +- name: verify test block storage volume idempotence + assert: + that: + - result is not changed + - result.vultr_block_storage.name == "{{ vultr_block_storage_name }}" + - result.vultr_block_storage.region == "{{ vultr_block_storage_region }}" + - result.vultr_block_storage.size == 10 + +- name: test destroy block storage volume in check mode + vultr_block_storage: + name: "{{ vultr_block_storage_name }}" + state: absent + register: result + check_mode: yes + +- name: verify test destroy block storage volume in check mode + assert: + that: + - result is changed + - result.vultr_block_storage.name == "{{ vultr_block_storage_name }}" + +- name: test destroy block storage volume + vultr_block_storage: + name: "{{ vultr_block_storage_name }}" + state: absent + register: result + +- name: verify test destroy an existing block storage volume + assert: + that: + - result is changed + - result.vultr_block_storage.name == "{{ vultr_block_storage_name }}" + +- name: test destroy an existing block storage volume idempotence + vultr_block_storage: + name: "{{ vultr_block_storage_name }}" + state: absent + register: result + +- name: verify test destroy an existing block storage volume idempotence + assert: + that: + - result is not changed diff --git a/test/legacy/vultr.yml b/test/legacy/vultr.yml index af46089688d..a807f65467e 100644 --- a/test/legacy/vultr.yml +++ b/test/legacy/vultr.yml @@ -6,6 +6,7 @@ connection: local roles: - { role: vultr_account_facts, tags: test_vultr_account_facts } + - { role: vultr_block_storage, tags: test_vultr_block_storage } - { role: vultr_dns_domain, tags: test_vultr_dns_domain } - { role: vultr_dns_domain_facts, tags: test_vultr_dns_domain_facts } - { role: vultr_dns_record, tags: test_vultr_dns_record }