diff --git a/lib/ansible/modules/extras/cloud/profitbricks/profitbricks.py b/lib/ansible/modules/extras/cloud/profitbricks/profitbricks.py index b0791f37e3a..0f4e8ff6fa2 100644 --- a/lib/ansible/modules/extras/cloud/profitbricks/profitbricks.py +++ b/lib/ansible/modules/extras/cloud/profitbricks/profitbricks.py @@ -195,14 +195,15 @@ EXAMPLES = ''' ''' -import re import uuid import time HAS_PB_SDK = True try: - from profitbricks.client import ProfitBricksService, Volume, Server, Datacenter, NIC, LAN + from profitbricks.client import ( + ProfitBricksService, Volume, Server, Datacenter, NIC, LAN + ) except ImportError: HAS_PB_SDK = False @@ -210,9 +211,6 @@ LOCATIONS = ['us/las', 'de/fra', 'de/fkb'] -uuid_match = re.compile( - '[\w]{8}-[\w]{4}-[\w]{4}-[\w]{4}-[\w]{12}', re.I) - def _wait_for_completion(profitbricks, promise, wait_timeout, msg): if not promise: return @@ -223,9 +221,9 @@ def _wait_for_completion(profitbricks, promise, wait_timeout, msg): request_id=promise['requestId'], status=True) - if operation_result['metadata']['status'] == "DONE": + if operation_result['metadata']['status'] == 'DONE': return - elif operation_result['metadata']['status'] == "FAILED": + elif operation_result['metadata']['status'] == 'FAILED': raise Exception( 'Request failed to complete ' + msg + ' "' + str( promise['requestId']) + '" to complete.') @@ -245,6 +243,7 @@ def _create_machine(module, profitbricks, datacenter, name): image_password = module.params.get('image_password') ssh_keys = module.params.get('ssh_keys') bus = module.params.get('bus') + nic_name = module.params.get('nic_name') lan = module.params.get('lan') assign_public_ip = module.params.get('assign_public_ip') subscription_user = module.params.get('subscription_user') @@ -284,6 +283,7 @@ def _create_machine(module, profitbricks, datacenter, name): bus=bus) n = NIC( + name=nic_name, lan=int(lan) ) @@ -311,6 +311,7 @@ def _create_machine(module, profitbricks, datacenter, name): except Exception as e: module.fail_json(msg="failed to create the new server: %s" % str(e)) else: + server_response['nic'] = server_response['entities']['nics']['items'][0] return server_response @@ -373,7 +374,7 @@ def create_virtual_machine(module, profitbricks): # Locate UUID for datacenter if referenced by name. datacenter_list = profitbricks.list_datacenters() - datacenter_id = _get_datacenter_id(datacenter_list, datacenter) + datacenter_id = _get_resource_id(datacenter_list, datacenter) if datacenter_id: datacenter_found = True @@ -409,14 +410,13 @@ def create_virtual_machine(module, profitbricks): server_list = profitbricks.list_servers(datacenter_id) for name in names: # Skip server creation if the server already exists. - if _get_server_id(server_list, name): + if _get_resource_id(server_list, name): continue create_response = _create_machine(module, profitbricks, str(datacenter_id), name) - nics = profitbricks.list_nics(datacenter_id, create_response['id']) - for n in nics['items']: - if lan == n['properties']['lan']: - create_response.update({'public_ip': n['properties']['ips'][0]}) + for nic in create_response['entities']['nics']['items']: + if lan == nic['properties']['lan']: + create_response.update({'public_ip': nic['properties']['ips'][0]}) virtual_machines.append(create_response) @@ -458,7 +458,7 @@ def remove_virtual_machine(module, profitbricks): # Locate UUID for datacenter if referenced by name. datacenter_list = profitbricks.list_datacenters() - datacenter_id = _get_datacenter_id(datacenter_list, datacenter) + datacenter_id = _get_resource_id(datacenter_list, datacenter) if not datacenter_id: module.fail_json(msg='Virtual data center \'%s\' not found.' % str(datacenter)) @@ -466,7 +466,7 @@ def remove_virtual_machine(module, profitbricks): server_list = profitbricks.list_servers(datacenter_id) for instance in instance_ids: # Locate UUID for server if referenced by name. - server_id = _get_server_id(server_list, instance) + server_id = _get_resource_id(server_list, instance) if server_id: # Remove the server's boot volume if remove_boot_volume: @@ -517,7 +517,7 @@ def startstop_machine(module, profitbricks, state): # Locate UUID for datacenter if referenced by name. datacenter_list = profitbricks.list_datacenters() - datacenter_id = _get_datacenter_id(datacenter_list, datacenter) + datacenter_id = _get_resource_id(datacenter_list, datacenter) if not datacenter_id: module.fail_json(msg='Virtual data center \'%s\' not found.' % str(datacenter)) @@ -525,7 +525,7 @@ def startstop_machine(module, profitbricks, state): server_list = profitbricks.list_servers(datacenter_id) for instance in instance_ids: # Locate UUID of server if referenced by name. - server_id = _get_server_id(server_list, instance) + server_id = _get_resource_id(server_list, instance) if server_id: _startstop_machine(module, profitbricks, datacenter_id, server_id) changed = True @@ -554,23 +554,14 @@ def startstop_machine(module, profitbricks, state): return (changed) -def _get_datacenter_id(datacenters, identity): - """ - Fetch and return datacenter UUID by datacenter name if found. - """ - for datacenter in datacenters['items']: - if identity in (datacenter['properties']['name'], datacenter['id']): - return datacenter['id'] - return None - - -def _get_server_id(servers, identity): +def _get_resource_id(resources, identity): """ - Fetch and return server UUID by server name if found. + Fetch and return the UUID of a resource regardless of whether the name or + UUID is passed. """ - for server in servers['items']: - if identity in (server['properties']['name'], server['id']): - return server['id'] + for resource in resources['items']: + if identity in (resource['properties']['name'], resource['id']): + return resource['id'] return None @@ -588,7 +579,8 @@ def main(): image_password=dict(default=None), ssh_keys=dict(type='list', default=[]), bus=dict(default='VIRTIO'), - lan=dict(default=1), + nic_name=dict(default=str(uuid.uuid4()).replace('-', '')[:10]), + lan=dict(type='int', default=1), count=dict(type='int', default=1), auto_increment=dict(type='bool', default=True), instance_ids=dict(type='list', default=[]), diff --git a/lib/ansible/modules/extras/cloud/profitbricks/profitbricks_firewall_rule.py b/lib/ansible/modules/extras/cloud/profitbricks/profitbricks_firewall_rule.py new file mode 100644 index 00000000000..902d6f94cc4 --- /dev/null +++ b/lib/ansible/modules/extras/cloud/profitbricks/profitbricks_firewall_rule.py @@ -0,0 +1,346 @@ +#!/usr/bin/python +# 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 . + +DOCUMENTATION = ''' +--- +module: profitbricks_firewall_rule +short_description: Create or remove a firewall rule. +description: + - This module allows you to create or remove a firewlal rule. This module has a dependency on profitbricks >= 1.0.0 +version_added: "2.0" +options: + datacenter: + description: + - The datacenter name or UUID in which to operate. + required: true + server: + description: + - The server name or UUID. + required: true + nic: + description: + - The NIC name or UUID. + required: true + name: + description: + - The name or UUID of the firewall rule. + required: false + protocol + description: + - The protocol for the firewall rule. + choices: [ "TCP", "UDP", "ICMP" ] + required: true + mac_source + description: + - Only traffic originating from the respective MAC address is allowed. Valid format: aa:bb:cc:dd:ee:ff. No value allows all source MAC addresses. + required: false + source_ip + description: + - Only traffic originating from the respective IPv4 address is allowed. No value allows all source IPs. + required: false + target_ip + description: + - In case the target NIC has multiple IP addresses, only traffic directed to the respective IP address of the NIC is allowed. No value allows all target IPs. + required: false + port_range_start + description: + - Defines the start range of the allowed port (from 1 to 65534) if protocol TCP or UDP is chosen. Leave value empty to allow all ports. + required: false + port_range_end + description: + - Defines the end range of the allowed port (from 1 to 65534) if the protocol TCP or UDP is chosen. Leave value empty to allow all ports. + required: false + icmp_type + description: + - Defines the allowed type (from 0 to 254) if the protocol ICMP is chosen. No value allows all types. + required: false + icmp_code + description: + - Defines the allowed code (from 0 to 254) if protocol ICMP is chosen. No value allows all codes. + required: false + subscription_user: + description: + - The ProfitBricks username. Overrides the PB_SUBSCRIPTION_ID environement variable. + required: false + subscription_password: + description: + - THe ProfitBricks password. Overrides the PB_PASSWORD environement variable. + required: false + wait: + description: + - wait for the operation to complete before returning + required: false + default: "yes" + choices: [ "yes", "no" ] + wait_timeout: + description: + - how long before wait gives up, in seconds + default: 600 + state: + description: + - Indicate desired state of the resource + required: false + default: 'present' + choices: ["present", "absent"] + +requirements: [ "profitbricks" ] +author: Ethan Devenport (ethand@stackpointcloud.com) +''' + +EXAMPLES = ''' + +# Create a firewall rule +- name: Create SSH firewall rule + profitbricks_firewall_rule: + datacenter: Virtual Datacenter + server: node001 + nic: 7341c2454f + name: Allow SSH + protocol: TCP + source_ip: 0.0.0.0 + port_range_start: 22 + port_range_end: 22 + state: present + +- name: Create ping firewall rule + profitbricks_firewall_rule: + datacenter: Virtual Datacenter + server: node001 + nic: 7341c2454f + name: Allow Ping + protocol: ICMP + source_ip: 0.0.0.0 + icmp_type: 8 + icmp_code: 0 + state: present + +# Remove a firewall rule +- name: Remove public ping firewall rule + profitbricks_firewall_rule: + datacenter: Virtual Datacenter + server: node001 + nic: aa6c261b9c + name: Allow Ping + state: absent +''' + +# import uuid +import time + +HAS_PB_SDK = True + +try: + from profitbricks.client import ProfitBricksService, FirewallRule +except ImportError: + HAS_PB_SDK = False + + +def _wait_for_completion(profitbricks, promise, wait_timeout, msg): + if not promise: return + wait_timeout = time.time() + wait_timeout + while wait_timeout > time.time(): + time.sleep(5) + operation_result = profitbricks.get_request( + request_id=promise['requestId'], + status=True) + + if operation_result['metadata']['status'] == 'DONE': + return + elif operation_result['metadata']['status'] == 'FAILED': + raise Exception( + 'Request failed to complete ' + msg + ' "' + str( + promise['requestId']) + '" to complete.') + + raise Exception( + 'Timed out waiting for async operation ' + msg + ' "' + str( + promise['requestId'] + ) + '" to complete.') + + +def create_firewall_rule(module, profitbricks): + """ + Creates a firewall rule. + + module : AnsibleModule object + profitbricks: authenticated profitbricks object. + + Returns: + True if the firewal rule creates, false otherwise + """ + datacenter = module.params.get('datacenter') + server = module.params.get('server') + nic = module.params.get('nic') + name = module.params.get('name') + protocol = module.params.get('protocol') + source_mac = module.params.get('source_mac') + source_ip = module.params.get('source_ip') + target_ip = module.params.get('target_ip') + port_range_start = module.params.get('port_range_start') + port_range_end = module.params.get('port_range_end') + icmp_type = module.params.get('icmp_type') + icmp_code = module.params.get('icmp_code') + wait = module.params.get('wait') + wait_timeout = module.params.get('wait_timeout') + + # Locate UUID for virtual datacenter + datacenter_list = profitbricks.list_datacenters() + datacenter_id = _get_resource_id(datacenter_list, datacenter) + if not datacenter_id: + module.fail_json(msg='Virtual data center \'%s\' not found.' % str(datacenter)) + + # Locate UUID for server + server_list = profitbricks.list_servers(datacenter_id) + server_id = _get_resource_id(server_list, server) + + # Locate UUID for NIC + nic_list = profitbricks.list_nics(datacenter_id, server_id) + nic_id = _get_resource_id(nic_list, nic) + + try: + profitbricks.update_nic(datacenter_id, server_id, nic_id, + firewall_active=True) + except Exception as e: + module.fail_json(msg='Unable to activate the NIC firewall.' % str(e)) + + f = FirewallRule( + name=name, + protocol=protocol, + mac_source=source_mac, + source_ip=source_ip, + target_ip=target_ip, + port_range_start=port_range_start, + port_range_end=port_range_end, + icmp_type=icmp_type, + icmp_code=icmp_code + ) + + try: + firewall_rule_response = profitbricks.create_firewall_rule( + datacenter_id, server_id, nic_id, f + ) + + if wait: + _wait_for_completion(profitbricks, firewall_rule_response, + wait_timeout, "create_firewall_rule") + return firewall_rule_response + + except Exception as e: + module.fail_json(msg="failed to create the firewall rule: %s" % str(e)) + + +def delete_firewall_rule(module, profitbricks): + """ + Removes a firewall rule + + module : AnsibleModule object + profitbricks: authenticated profitbricks object. + + Returns: + True if the firewall rule was removed, false otherwise + """ + datacenter = module.params.get('datacenter') + server = module.params.get('server') + nic = module.params.get('nic') + name = module.params.get('name') + + # Locate UUID for virtual datacenter + datacenter_list = profitbricks.list_datacenters() + datacenter_id = _get_resource_id(datacenter_list, datacenter) + + # Locate UUID for server + server_list = profitbricks.list_servers(datacenter_id) + server_id = _get_resource_id(server_list, server) + + # Locate UUID for NIC + nic_list = profitbricks.list_nics(datacenter_id, server_id) + nic_id = _get_resource_id(nic_list, nic) + + # Locate UUID for firewall rule + firewall_rule_list = profitbricks.get_firewall_rules(datacenter_id, server_id, nic_id) + firewall_rule_id = _get_resource_id(firewall_rule_list, name) + + try: + firewall_rule_response = profitbricks.delete_firewall_rule( + datacenter_id, server_id, nic_id, firewall_rule_id + ) + return firewall_rule_response + except Exception as e: + module.fail_json(msg="failed to remove the firewall rule: %s" % str(e)) + + +def _get_resource_id(resource_list, identity): + """ + Fetch and return the UUID of a resource regardless of whether the name or + UUID is passed. + """ + for resource in resource_list['items']: + if identity in (resource['properties']['name'], resource['id']): + return resource['id'] + return None + + +def main(): + module = AnsibleModule( + argument_spec=dict( + datacenter=dict(type='str', required=True), + server=dict(type='str', required=True), + nic=dict(type='str', required=True), + name=dict(type='str', required=True), + protocol=dict(type='str', required=False), + source_mac=dict(type='str', default=None), + source_ip=dict(type='str', default=None), + target_ip=dict(type='str', default=None), + port_range_start=dict(type='int', default=None), + port_range_end=dict(type='int', default=None), + icmp_type=dict(type='int', default=None), + icmp_code=dict(type='int', default=None), + subscription_user=dict(type='str', required=True), + subscription_password=dict(type='str', required=True), + wait=dict(type='bool', default=True), + wait_timeout=dict(type='int', default=600), + state=dict(default='present'), + ) + ) + + if not HAS_PB_SDK: + module.fail_json(msg='profitbricks required for this module') + + subscription_user = module.params.get('subscription_user') + subscription_password = module.params.get('subscription_password') + + profitbricks = ProfitBricksService( + username=subscription_user, + password=subscription_password) + + state = module.params.get('state') + + if state == 'absent': + try: + (changed) = delete_firewall_rule(module, profitbricks) + module.exit_json(changed=changed) + except Exception as e: + module.fail_json(msg='failed to set firewall rule state: %s' % str(e)) + + elif state == 'present': + try: + (firewall_rule_dict) = create_firewall_rule(module, profitbricks) + module.exit_json(firewall_rules=firewall_rule_dict) + except Exception as e: + module.fail_json(msg='failed to set firewall rules state: %s' % str(e)) + +from ansible.module_utils.basic import * + +main() diff --git a/lib/ansible/modules/extras/cloud/profitbricks/profitbricks_nic.py b/lib/ansible/modules/extras/cloud/profitbricks/profitbricks_nic.py index 902d5266843..d13554cf43a 100644 --- a/lib/ansible/modules/extras/cloud/profitbricks/profitbricks_nic.py +++ b/lib/ansible/modules/extras/cloud/profitbricks/profitbricks_nic.py @@ -84,23 +84,18 @@ EXAMPLES = ''' name: 7341c2454f wait_timeout: 500 state: absent - ''' -import re import uuid import time HAS_PB_SDK = True try: - from profitbricks.client import ProfitBricksService, NIC + from profitbricks.client import ProfitBricksService, NIC, FirewallRule except ImportError: HAS_PB_SDK = False -uuid_match = re.compile( - '[\w]{8}-[\w]{4}-[\w]{4}-[\w]{4}-[\w]{12}', re.I) - def _wait_for_completion(profitbricks, promise, wait_timeout, msg): if not promise: return @@ -111,9 +106,9 @@ def _wait_for_completion(profitbricks, promise, wait_timeout, msg): request_id=promise['requestId'], status=True) - if operation_result['metadata']['status'] == "DONE": + if operation_result['metadata']['status'] == 'DONE': return - elif operation_result['metadata']['status'] == "FAILED": + elif operation_result['metadata']['status'] == 'FAILED': raise Exception( 'Request failed to complete ' + msg + ' "' + str( promise['requestId']) + '" to complete.') @@ -123,6 +118,7 @@ def _wait_for_completion(profitbricks, promise, wait_timeout, msg): promise['requestId'] ) + '" to complete.') + def create_nic(module, profitbricks): """ Creates a NIC. @@ -141,28 +137,22 @@ def create_nic(module, profitbricks): wait_timeout = module.params.get('wait_timeout') # Locate UUID for Datacenter - if not (uuid_match.match(datacenter)): - datacenter_list = profitbricks.list_datacenters() - for d in datacenter_list['items']: - dc = profitbricks.get_datacenter(d['id']) - if datacenter == dc['properties']['name']: - datacenter = d['id'] - break + datacenter_list = profitbricks.list_datacenters() + datacenter_id = _get_resource_id(datacenter_list, datacenter) + if not datacenter_id: + module.fail_json(msg='Virtual data center \'%s\' not found.' % str(datacenter)) # Locate UUID for Server - if not (uuid_match.match(server)): - server_list = profitbricks.list_servers(datacenter) - for s in server_list['items']: - if server == s['properties']['name']: - server = s['id'] - break - try: - n = NIC( - name=name, - lan=lan - ) + server_list = profitbricks.list_servers(datacenter_id) + server_id = _get_resource_id(server_list, server) + + n = NIC( + name=name, + lan=lan + ) - nic_response = profitbricks.create_nic(datacenter, server, n) + try: + nic_response = profitbricks.create_nic(datacenter_id, server_id, n) if wait: _wait_for_completion(profitbricks, nic_response, @@ -173,6 +163,7 @@ def create_nic(module, profitbricks): except Exception as e: module.fail_json(msg="failed to create the NIC: %s" % str(e)) + def delete_nic(module, profitbricks): """ Removes a NIC @@ -188,53 +179,44 @@ def delete_nic(module, profitbricks): name = module.params.get('name') # Locate UUID for Datacenter - if not (uuid_match.match(datacenter)): - datacenter_list = profitbricks.list_datacenters() - for d in datacenter_list['items']: - dc = profitbricks.get_datacenter(d['id']) - if datacenter == dc['properties']['name']: - datacenter = d['id'] - break + datacenter_list = profitbricks.list_datacenters() + datacenter_id = _get_resource_id(datacenter_list, datacenter) + if not datacenter_id: + module.fail_json(msg='Virtual data center \'%s\' not found.' % str(datacenter)) # Locate UUID for Server - server_found = False - if not (uuid_match.match(server)): - server_list = profitbricks.list_servers(datacenter) - for s in server_list['items']: - if server == s['properties']['name']: - server_found = True - server = s['id'] - break - - if not server_found: - return False + server_list = profitbricks.list_servers(datacenter_id) + server_id = _get_resource_id(server_list, server) # Locate UUID for NIC - nic_found = False - if not (uuid_match.match(name)): - nic_list = profitbricks.list_nics(datacenter, server) - for n in nic_list['items']: - if name == n['properties']['name']: - nic_found = True - name = n['id'] - break - - if not nic_found: - return False + nic_list = profitbricks.list_nics(datacenter_id, server_id) + nic_id = _get_resource_id(nic_list, name) try: - nic_response = profitbricks.delete_nic(datacenter, server, name) + nic_response = profitbricks.delete_nic(datacenter_id, server_id, nic_id) return nic_response except Exception as e: module.fail_json(msg="failed to remove the NIC: %s" % str(e)) + +def _get_resource_id(resource_list, identity): + """ + Fetch and return the UUID of a resource regardless of whether the name or + UUID is passed. + """ + for resource in resource_list['items']: + if identity in (resource['properties']['name'], resource['id']): + return resource['id'] + return None + + def main(): module = AnsibleModule( argument_spec=dict( datacenter=dict(), server=dict(), - name=dict(default=str(uuid.uuid4()).replace('-','')[:10]), - lan=dict(), + name=dict(default=str(uuid.uuid4()).replace('-', '')[:10]), + lan=dict(type='int'), subscription_user=dict(), subscription_password=dict(), wait=dict(type='bool', default=True), @@ -255,7 +237,6 @@ def main(): if not module.params.get('server'): module.fail_json(msg='server parameter is required') - subscription_user = module.params.get('subscription_user') subscription_password = module.params.get('subscription_password') @@ -281,10 +262,10 @@ def main(): try: (nic_dict) = create_nic(module, profitbricks) - module.exit_json(nics=nic_dict) + module.exit_json(nic=nic_dict) except Exception as e: module.fail_json(msg='failed to set nic state: %s' % str(e)) from ansible.module_utils.basic import * -main() \ No newline at end of file +main()