From fa41ccd59b502cb7539bfa6b6946afdc0848329e Mon Sep 17 00:00:00 2001 From: Ethan Devenport Date: Fri, 29 Jul 2016 04:37:42 +0000 Subject: [PATCH 01/10] Additional provider features added and fixed some bugs. * Added support for SSH keys, image passwords, SSD disk type, and CPU family. * Adjusted server create so that IP address is returned in response. * Restructured remove server method(s) to handle change status properly, gracefully handle missing servers, and improve overall performance. * Prevent duplicate server names from being provisioned so removals can be handled appropriately. * Fixed a bug in the count increment being a string rather than an integer. * Fixed issue with create_volume returning invalid response. * Fixed type bug in volume instance_ids for volume removal and improved volume management. * Fixed type bug in instance_ids for proper server removal and moved boot volume creation into composite server build request. * General clean up. --- cloud/profitbricks/profitbricks.py | 318 ++++++++++++---------- cloud/profitbricks/profitbricks_volume.py | 59 +++- 2 files changed, 222 insertions(+), 155 deletions(-) diff --git a/cloud/profitbricks/profitbricks.py b/cloud/profitbricks/profitbricks.py index 556c652828e..0a1e299949d 100644 --- a/cloud/profitbricks/profitbricks.py +++ b/cloud/profitbricks/profitbricks.py @@ -31,13 +31,21 @@ options: description: - The name of the virtual machine. required: true - image: + image: description: - The system image ID for creating the virtual machine, e.g. a3eae284-a2fe-11e4-b187-5f1f641608c8. required: true + image_password: + description: + - Password set for the administrative user. + required: false + ssh_keys: + description: + - Public SSH keys allowing access to the virtual machine. + required: false datacenter: description: - - The Datacenter to provision this virtual machine. + - The datacenter to provision this virtual machine. required: false default: null cores: @@ -50,6 +58,12 @@ options: - The amount of memory to allocate to the virtual machine. required: false default: 2048 + cpu_family: + description: + - The CPU family type to allocate to the virtual machine. + required: false + default: AMD_OPTERON + choices: [ "AMD_OPTERON", "INTEL_XEON" ] volume_size: description: - The size in GB of the boot volume. @@ -72,7 +86,7 @@ options: default: 1 location: description: - - The datacenter location. Use only if you want to create the Datacenter or else this value is ignored. + - The datacenter location. Use only if you want to create the Datacenter or else this value is ignored. required: false default: us/las choices: [ "us/las", "us/lasdev", "de/fra", "de/fkb" ] @@ -129,7 +143,7 @@ EXAMPLES = ''' # Note: These examples do not set authentication details, see the AWS Guide for details. -# Provisioning example. This will create three servers and enumerate their names. +# Provisioning example. This will create three servers and enumerate their names. - profitbricks: datacenter: Tardis One @@ -137,6 +151,7 @@ EXAMPLES = ''' cores: 4 ram: 2048 volume_size: 50 + cpu_family: INTEL_XEON image: a3eae284-a2fe-11e4-b187-5f1f641608c8 location: us/las count: 3 @@ -218,11 +233,15 @@ def _wait_for_completion(profitbricks, promise, wait_timeout, msg): promise['requestId'] ) + '" to complete.') + def _create_machine(module, profitbricks, datacenter, name): - image = module.params.get('image') cores = module.params.get('cores') ram = module.params.get('ram') + cpu_family = module.params.get('cpu_family') volume_size = module.params.get('volume_size') + disk_type = module.params.get('disk_type') + image_password = module.params.get('image_password') + ssh_keys = module.params.get('ssh_keys') bus = module.params.get('bus') lan = module.params.get('lan') assign_public_ip = module.params.get('assign_public_ip') @@ -234,26 +253,6 @@ def _create_machine(module, profitbricks, datacenter, name): wait = module.params.get('wait') wait_timeout = module.params.get('wait_timeout') - try: - # Generate name, but grab first 10 chars so we don't - # screw up the uuid match routine. - v = Volume( - name=str(uuid.uuid4()).replace('-','')[:10], - size=volume_size, - image=image, - bus=bus) - - volume_response = profitbricks.create_volume( - datacenter_id=datacenter, volume=v) - - # We're forced to wait on the volume creation since - # server create relies upon this existing. - - _wait_for_completion(profitbricks, volume_response, - wait_timeout, "create_volume") - except Exception as e: - module.fail_json(msg="failed to create the new volume: %s" % str(e)) - if assign_public_ip: public_found = False @@ -269,81 +268,64 @@ def _create_machine(module, profitbricks, datacenter, name): public=True) lan_response = profitbricks.create_lan(datacenter, i) - - lan = lan_response['id'] - _wait_for_completion(profitbricks, lan_response, wait_timeout, "_create_machine") + lan = lan_response['id'] - try: - n = NIC( - lan=int(lan) - ) - - nics = [n] + v = Volume( + name=str(uuid.uuid4()).replace('-', '')[:10], + size=volume_size, + image=image, + image_password=image_password, + ssh_keys=ssh_keys, + disk_type=disk_type, + bus=bus) + + n = NIC( + lan=int(lan) + ) - s = Server( - name=name, - ram=ram, - cores=cores, - nics=nics, - boot_volume_id=volume_response['id'] - ) + s = Server( + name=name, + ram=ram, + cores=cores, + cpu_family=cpu_family, + create_volumes=[v], + nics=[n], + ) - server_response = profitbricks.create_server( + try: + create_server_response = profitbricks.create_server( datacenter_id=datacenter, server=s) - if wait: - _wait_for_completion(profitbricks, server_response, - wait_timeout, "create_virtual_machine") - + _wait_for_completion(profitbricks, create_server_response, + wait_timeout, "create_virtual_machine") - return (server_response) + server_response = profitbricks.get_server( + datacenter_id=datacenter, + server_id=create_server_response['id'], + depth=3 + ) except Exception as e: module.fail_json(msg="failed to create the new server: %s" % str(e)) + else: + return server_response -def _remove_machine(module, profitbricks, datacenter, name): - remove_boot_volume = module.params.get('remove_boot_volume') - wait = module.params.get('wait') - wait_timeout = module.params.get('wait_timeout') - changed = False - - # User provided the actual UUID instead of the name. - try: - if remove_boot_volume: - # Collect information needed for later. - server = profitbricks.get_server(datacenter, name) - volume_id = server['properties']['bootVolume']['href'].split('/')[7] - - server_response = profitbricks.delete_server(datacenter, name) - changed = True - - except Exception as e: - module.fail_json(msg="failed to terminate the virtual server: %s" % str(e)) - - # Remove the bootVolume - if remove_boot_volume: - try: - volume_response = profitbricks.delete_volume(datacenter, volume_id) - - except Exception as e: - module.fail_json(msg="failed to remove the virtual server's bootvolume: %s" % str(e)) - - return changed -def _startstop_machine(module, profitbricks, datacenter, name): +def _startstop_machine(module, profitbricks, datacenter_id, server_id): state = module.params.get('state') try: if state == 'running': - profitbricks.start_server(datacenter, name) + profitbricks.start_server(datacenter_id, server_id) else: - profitbricks.stop_server(datacenter, name) + profitbricks.stop_server(datacenter_id, server_id) return True except Exception as e: module.fail_json(msg="failed to start or stop the virtual machine %s: %s" % (name, str(e))) + def _create_datacenter(module, profitbricks): datacenter = module.params.get('datacenter') location = module.params.get('location') @@ -364,6 +346,7 @@ def _create_datacenter(module, profitbricks): except Exception as e: module.fail_json(msg="failed to create the new server(s): %s" % str(e)) + def create_virtual_machine(module, profitbricks): """ Create new virtual machine @@ -386,19 +369,15 @@ def create_virtual_machine(module, profitbricks): virtual_machines = [] virtual_machine_ids = [] - # 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'] - datacenter_found = True - break + # Locate UUID for datacenter if referenced by name. + datacenter_list = profitbricks.list_datacenters() + datacenter_id = _get_datacenter_id(datacenter_list, datacenter) + if datacenter_id: + datacenter_found = True if not datacenter_found: datacenter_response = _create_datacenter(module, profitbricks) - datacenter = datacenter_response['id'] + datacenter_id = datacenter_response['id'] _wait_for_completion(profitbricks, datacenter_response, wait_timeout, "create_virtual_machine") @@ -415,24 +394,31 @@ def create_virtual_machine(module, profitbricks): else: module.fail_json(msg=e.message) - number_range = xrange(count_offset,count_offset + count + len(numbers)) + number_range = xrange(count_offset, count_offset + count + len(numbers)) available_numbers = list(set(number_range).difference(numbers)) names = [] numbers_to_use = available_numbers[:count] for number in numbers_to_use: names.append(name % number) else: - names = [name] * count + names = [name] - for name in names: - create_response = _create_machine(module, profitbricks, str(datacenter), name) - nics = profitbricks.list_nics(datacenter,create_response['id']) + # Prefetch a list of servers for later comparison. + 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): + 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] }) + create_response.update({'public_ip': n['properties']['ips'][0]}) virtual_machines.append(create_response) - failed = False + + failed = False results = { 'failed': failed, @@ -445,9 +431,10 @@ def create_virtual_machine(module, profitbricks): return results + def remove_virtual_machine(module, profitbricks): """ - Removes a virtual machine. + Removes a virtual machine. This will remove the virtual machine along with the bootVolume. @@ -459,36 +446,56 @@ def remove_virtual_machine(module, profitbricks): Returns: True if a new virtual server was deleted, false otherwise """ + datacenter = module.params.get('datacenter') + instance_ids = module.params.get('instance_ids') + remove_boot_volume = module.params.get('remove_boot_volume') + changed = False + if not isinstance(module.params.get('instance_ids'), list) or len(module.params.get('instance_ids')) < 1: module.fail_json(msg='instance_ids should be a list of virtual machine ids or names, aborting') - datacenter = module.params.get('datacenter') - instance_ids = module.params.get('instance_ids') + # Locate UUID for datacenter if referenced by name. + datacenter_list = profitbricks.list_datacenters() + datacenter_id = _get_datacenter_id(datacenter_list, datacenter) + if not datacenter_id: + module.fail_json(msg='Virtual data center \'%s\' not found.' % str(datacenter)) + + # Prefetch server list for later comparison. + 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) + if server_id: + # Remove the server's boot volume + if remove_boot_volume: + _remove_boot_volume(module, profitbricks, datacenter_id, server_id) + + # Remove the server + try: + server_response = profitbricks.delete_server(datacenter_id, server_id) + except Exception as e: + module.fail_json(msg="failed to terminate the virtual server: %s" % str(e)) + else: + changed = True - # 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 + return changed - for n in instance_ids: - if(uuid_match.match(n)): - _remove_machine(module, profitbricks, d['id'], n) - else: - servers = profitbricks.list_servers(d['id']) - for s in servers['items']: - if n == s['properties']['name']: - server_id = s['id'] +def _remove_boot_volume(module, profitbricks, datacenter_id, server_id): + """ + Remove the boot volume from the server + """ + try: + server = profitbricks.get_server(datacenter_id, server_id) + volume_id = server['properties']['bootVolume']['id'] + volume_response = profitbricks.delete_volume(datacenter_id, volume_id) + except Exception as e: + module.fail_json(msg="failed to remove the server's boot volume: %s" % str(e)) - _remove_machine(module, profitbricks, datacenter, server_id) def startstop_machine(module, profitbricks, state): """ - Starts or Stops a virtual machine. + Starts or Stops a virtual machine. module : AnsibleModule object profitbricks: authenticated profitbricks object. @@ -506,41 +513,32 @@ def startstop_machine(module, profitbricks, state): datacenter = module.params.get('datacenter') instance_ids = module.params.get('instance_ids') - # 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 - - for n in instance_ids: - if(uuid_match.match(n)): - _startstop_machine(module, profitbricks, datacenter, n) - + # Locate UUID for datacenter if referenced by name. + datacenter_list = profitbricks.list_datacenters() + datacenter_id = _get_datacenter_id(datacenter_list, datacenter) + if not datacenter_id: + module.fail_json(msg='Virtual data center \'%s\' not found.' % str(datacenter)) + + # Prefetch server list for later comparison. + 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) + if server_id: + _startstop_machine(module, profitbricks, datacenter_id, server_id) changed = True - else: - servers = profitbricks.list_servers(d['id']) - - for s in servers['items']: - if n == s['properties']['name']: - server_id = s['id'] - _startstop_machine(module, profitbricks, datacenter, server_id) - - changed = True if wait: wait_timeout = time.time() + wait_timeout while wait_timeout > time.time(): matched_instances = [] - for res in profitbricks.list_servers(datacenter)['items']: + for res in profitbricks.list_servers(datacenter_id)['items']: if state == 'running': if res['properties']['vmState'].lower() == state: matched_instances.append(res) elif state == 'stopped': if res['properties']['vmState'].lower() == 'shutoff': - matched_instances.append(res) + matched_instances.append(res) if len(matched_instances) < len(instance_ids): time.sleep(5) @@ -549,10 +547,31 @@ def startstop_machine(module, profitbricks, state): if wait_timeout <= time.time(): # waiting took too long - module.fail_json(msg = "wait for virtual machine state timeout on %s" % time.asctime()) + module.fail_json(msg="wait for virtual machine state timeout on %s" % time.asctime()) 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): + """ + Fetch and return server UUID by server name if found. + """ + for server in servers['items']: + if identity in (server['properties']['name'], server['id']): + return server['id'] + return None + + def main(): module = AnsibleModule( argument_spec=dict( @@ -561,12 +580,16 @@ def main(): image=dict(), cores=dict(default=2), ram=dict(default=2048), + cpu_family=dict(default='AMD_OPTERON'), volume_size=dict(default=10), + disk_type=dict(default='HDD'), + image_password=dict(default=None), + ssh_keys=dict(type='list', default=[]), bus=dict(default='VIRTIO'), lan=dict(default=1), - count=dict(default=1), + count=dict(type='int', default=1), auto_increment=dict(type='bool', default=True), - instance_ids=dict(), + instance_ids=dict(type='list', default=[]), subscription_user=dict(), subscription_password=dict(), location=dict(choices=LOCATIONS, default='us/las'), @@ -594,7 +617,7 @@ def main(): if state == 'absent': if not module.params.get('datacenter'): - module.fail_json(msg='datacenter parameter is required ' + + module.fail_json(msg='datacenter parameter is required ' + 'for running or stopping machines.') try: @@ -605,7 +628,7 @@ def main(): elif state in ('running', 'stopped'): if not module.params.get('datacenter'): - module.fail_json(msg='datacenter parameter is required for ' + + module.fail_json(msg='datacenter parameter is required for ' + 'running or stopping machines.') try: (changed) = startstop_machine(module, profitbricks, state) @@ -619,10 +642,10 @@ def main(): if not module.params.get('image'): module.fail_json(msg='image parameter is required for new instance') if not module.params.get('subscription_user'): - module.fail_json(msg='subscription_user parameter is ' + + module.fail_json(msg='subscription_user parameter is ' + 'required for new instance') if not module.params.get('subscription_password'): - module.fail_json(msg='subscription_password parameter is ' + + module.fail_json(msg='subscription_password parameter is ' + 'required for new instance') try: @@ -634,4 +657,3 @@ def main(): from ansible.module_utils.basic import * main() - diff --git a/cloud/profitbricks/profitbricks_volume.py b/cloud/profitbricks/profitbricks_volume.py index 89a69d5e61a..802511cc930 100644 --- a/cloud/profitbricks/profitbricks_volume.py +++ b/cloud/profitbricks/profitbricks_volume.py @@ -45,11 +45,20 @@ options: description: - The system image ID for the volume, e.g. a3eae284-a2fe-11e4-b187-5f1f641608c8. This can also be a snapshot image ID. required: true + image_password: + description: + - Password set for the administrative user. + required: false + ssh_keys: + description: + - Public SSH keys allowing access to the virtual machine. + required: false disk_type: description: - - The disk type. Currently only HDD. + - The disk type of the volume. required: false default: HDD + choices: [ "HDD", "SSD" ] licence_type: description: - The licence type for the volume. This is used when the image is non-standard. @@ -163,6 +172,8 @@ def _create_volume(module, profitbricks, datacenter, name): size = module.params.get('size') bus = module.params.get('bus') image = module.params.get('image') + image_password = module.params.get('image_password') + ssh_keys = module.params.get('ssh_keys') disk_type = module.params.get('disk_type') licence_type = module.params.get('licence_type') wait_timeout = module.params.get('wait_timeout') @@ -174,6 +185,8 @@ def _create_volume(module, profitbricks, datacenter, name): size=size, bus=bus, image=image, + image_password=image_password, + ssh_keys=ssh_keys, disk_type=disk_type, licence_type=licence_type ) @@ -250,9 +263,10 @@ def create_volume(module, profitbricks): else: names = [name] * count - for name in names: + for name in names: create_response = _create_volume(module, profitbricks, str(datacenter), name) volumes.append(create_response) + _attach_volume(module, profitbricks, datacenter, create_response['id']) failed = False results = { @@ -308,19 +322,50 @@ def delete_volume(module, profitbricks): return changed +def _attach_volume(module, profitbricks, datacenter, volume): + """ + Attaches a volume. + + This will attach a volume to the server. + + module : AnsibleModule object + profitbricks: authenticated profitbricks object. + + Returns: + True if the volume was attached, false otherwise + """ + server = module.params.get('server') + + # Locate UUID for Server + if 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: + return profitbricks.attach_volume(datacenter, server, volume) + except Exception as e: + module.fail_json(msg='failed to attach volume: %s' % str(e)) + def main(): module = AnsibleModule( argument_spec=dict( datacenter=dict(), + server=dict(), name=dict(), size=dict(default=10), bus=dict(default='VIRTIO'), image=dict(), + image_password=dict(default=None), + ssh_keys=dict(type='list', default=[]), disk_type=dict(default='HDD'), licence_type=dict(default='UNKNOWN'), - count=dict(default=1), + count=dict(type='int', default=1), auto_increment=dict(type='bool', default=True), - instance_ids=dict(), + instance_ids=dict(type='list', default=[]), subscription_user=dict(), subscription_password=dict(), wait=dict(type='bool', default=True), @@ -360,11 +405,11 @@ def main(): module.fail_json(msg='name parameter is required for new instance') try: - (failed, volume_dict_array) = create_volume(module, profitbricks) - module.exit_json(failed=failed, volumes=volume_dict_array) + (volume_dict_array) = create_volume(module, profitbricks) + module.exit_json(**volume_dict_array) except Exception as e: module.fail_json(msg='failed to set volume state: %s' % str(e)) from ansible.module_utils.basic import * -main() \ No newline at end of file +main() From 727eaa219d7c61797adb04c9938835b82696e73c Mon Sep 17 00:00:00 2001 From: Ethan Devenport Date: Tue, 23 Aug 2016 07:52:39 +0000 Subject: [PATCH 02/10] Removed us/lasdev datacenter which the cloud provider no longer maintains. --- cloud/profitbricks/profitbricks.py | 5 ++--- cloud/profitbricks/profitbricks_datacenter.py | 9 ++++----- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/cloud/profitbricks/profitbricks.py b/cloud/profitbricks/profitbricks.py index 0a1e299949d..e01d74777d4 100644 --- a/cloud/profitbricks/profitbricks.py +++ b/cloud/profitbricks/profitbricks.py @@ -89,7 +89,7 @@ options: - The datacenter location. Use only if you want to create the Datacenter or else this value is ignored. required: false default: us/las - choices: [ "us/las", "us/lasdev", "de/fra", "de/fkb" ] + choices: [ "us/las", "de/fra", "de/fkb" ] assign_public_ip: description: - This will assign the machine to the public LAN. If no LAN exists with public Internet access it is created. @@ -205,8 +205,7 @@ except ImportError: LOCATIONS = ['us/las', 'de/fra', - 'de/fkb', - 'us/lasdev'] + 'de/fkb'] uuid_match = re.compile( '[\w]{8}-[\w]{4}-[\w]{4}-[\w]{4}-[\w]{12}', re.I) diff --git a/cloud/profitbricks/profitbricks_datacenter.py b/cloud/profitbricks/profitbricks_datacenter.py index cd0e38ee383..0b21d3e4cd6 100644 --- a/cloud/profitbricks/profitbricks_datacenter.py +++ b/cloud/profitbricks/profitbricks_datacenter.py @@ -35,7 +35,7 @@ options: - The datacenter location. required: false default: us/las - choices: [ "us/las", "us/lasdev", "de/fra", "de/fkb" ] + choices: [ "us/las", "de/fra", "de/fkb" ] subscription_user: description: - The ProfitBricks username. Overrides the PB_SUBSCRIPTION_ID environement variable. @@ -94,8 +94,7 @@ except ImportError: LOCATIONS = ['us/las', 'de/fra', - 'de/fkb', - 'us/lasdev'] + 'de/fkb'] uuid_match = re.compile( '[\w]{8}-[\w]{4}-[\w]{4}-[\w]{4}-[\w]{12}', re.I) @@ -206,7 +205,7 @@ def main(): argument_spec=dict( name=dict(), description=dict(), - location=dict(choices=LOCATIONS, default='us/lasdev'), + location=dict(choices=LOCATIONS, default='us/las'), subscription_user=dict(), subscription_password=dict(), wait=dict(type='bool', default=True), @@ -256,4 +255,4 @@ def main(): from ansible.module_utils.basic import * -main() \ No newline at end of file +main() From 1631c717ff9cd0ae67c919d36400e39cd8e9140b Mon Sep 17 00:00:00 2001 From: Ethan Devenport Date: Tue, 23 Aug 2016 08:00:41 +0000 Subject: [PATCH 03/10] Included version_added for new options. --- cloud/profitbricks/profitbricks.py | 3 +++ cloud/profitbricks/profitbricks_volume.py | 2 ++ 2 files changed, 5 insertions(+) diff --git a/cloud/profitbricks/profitbricks.py b/cloud/profitbricks/profitbricks.py index e01d74777d4..b0791f37e3a 100644 --- a/cloud/profitbricks/profitbricks.py +++ b/cloud/profitbricks/profitbricks.py @@ -39,10 +39,12 @@ options: description: - Password set for the administrative user. required: false + version_added: '2.2' ssh_keys: description: - Public SSH keys allowing access to the virtual machine. required: false + version_added: '2.2' datacenter: description: - The datacenter to provision this virtual machine. @@ -64,6 +66,7 @@ options: required: false default: AMD_OPTERON choices: [ "AMD_OPTERON", "INTEL_XEON" ] + version_added: '2.2' volume_size: description: - The size in GB of the boot volume. diff --git a/cloud/profitbricks/profitbricks_volume.py b/cloud/profitbricks/profitbricks_volume.py index 802511cc930..6b7877f31cc 100644 --- a/cloud/profitbricks/profitbricks_volume.py +++ b/cloud/profitbricks/profitbricks_volume.py @@ -49,10 +49,12 @@ options: description: - Password set for the administrative user. required: false + version_added: '2.2' ssh_keys: description: - Public SSH keys allowing access to the virtual machine. required: false + version_added: '2.2' disk_type: description: - The disk type of the volume. From 464cbb89f225578386a830624633a55e39054544 Mon Sep 17 00:00:00 2001 From: Ethan Devenport Date: Mon, 29 Aug 2016 05:56:26 +0000 Subject: [PATCH 04/10] Added support for firewall rules, consolidated resource UUID retrieval methods for server and NIC modules, and set LAN type to int. --- cloud/profitbricks/profitbricks.py | 58 ++- .../profitbricks_firewall_rule.py | 346 ++++++++++++++++++ cloud/profitbricks/profitbricks_nic.py | 105 +++--- 3 files changed, 414 insertions(+), 95 deletions(-) create mode 100644 cloud/profitbricks/profitbricks_firewall_rule.py diff --git a/cloud/profitbricks/profitbricks.py b/cloud/profitbricks/profitbricks.py index b0791f37e3a..0f4e8ff6fa2 100644 --- a/cloud/profitbricks/profitbricks.py +++ b/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/cloud/profitbricks/profitbricks_firewall_rule.py b/cloud/profitbricks/profitbricks_firewall_rule.py new file mode 100644 index 00000000000..902d6f94cc4 --- /dev/null +++ b/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/cloud/profitbricks/profitbricks_nic.py b/cloud/profitbricks/profitbricks_nic.py index 902d5266843..d13554cf43a 100644 --- a/cloud/profitbricks/profitbricks_nic.py +++ b/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() From ea46cbef347457bb0895ce258459de76fad8a476 Mon Sep 17 00:00:00 2001 From: Ethan Devenport Date: Mon, 29 Aug 2016 06:18:25 +0000 Subject: [PATCH 05/10] Minor documentation corrections. --- cloud/profitbricks/profitbricks.py | 4 ++++ cloud/profitbricks/profitbricks_firewall_rule.py | 16 ++++++++-------- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/cloud/profitbricks/profitbricks.py b/cloud/profitbricks/profitbricks.py index 0f4e8ff6fa2..f8d6eb78fd0 100644 --- a/cloud/profitbricks/profitbricks.py +++ b/cloud/profitbricks/profitbricks.py @@ -98,6 +98,10 @@ options: - This will assign the machine to the public LAN. If no LAN exists with public Internet access it is created. required: false default: false + nic_name: + description: + - The name of the default NIC. + required: false lan: description: - The ID of the LAN you wish to add the servers to. diff --git a/cloud/profitbricks/profitbricks_firewall_rule.py b/cloud/profitbricks/profitbricks_firewall_rule.py index 902d6f94cc4..f04d2a7f2b1 100644 --- a/cloud/profitbricks/profitbricks_firewall_rule.py +++ b/cloud/profitbricks/profitbricks_firewall_rule.py @@ -38,36 +38,36 @@ options: description: - The name or UUID of the firewall rule. required: false - protocol + protocol: description: - The protocol for the firewall rule. choices: [ "TCP", "UDP", "ICMP" ] required: true - mac_source + 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 + source_ip: description: - Only traffic originating from the respective IPv4 address is allowed. No value allows all source IPs. required: false - target_ip + 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 + 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 + 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 + 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 + icmp_code: description: - Defines the allowed code (from 0 to 254) if protocol ICMP is chosen. No value allows all codes. required: false From 681ad6f2cc47e86b3d28483c3e8bfe20e21016c6 Mon Sep 17 00:00:00 2001 From: Ethan Devenport Date: Mon, 29 Aug 2016 06:30:54 +0000 Subject: [PATCH 06/10] Some further documentation updates including version. --- cloud/profitbricks/profitbricks.py | 3 ++- cloud/profitbricks/profitbricks_firewall_rule.py | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/cloud/profitbricks/profitbricks.py b/cloud/profitbricks/profitbricks.py index f8d6eb78fd0..f3016fd13ca 100644 --- a/cloud/profitbricks/profitbricks.py +++ b/cloud/profitbricks/profitbricks.py @@ -102,6 +102,7 @@ options: description: - The name of the default NIC. required: false + version_added: '2.2' lan: description: - The ID of the LAN you wish to add the servers to. @@ -114,7 +115,7 @@ options: default: null subscription_password: description: - - THe ProfitBricks password. Overrides the PB_PASSWORD environement variable. + - The ProfitBricks password. Overrides the PB_PASSWORD environement variable. required: false default: null wait: diff --git a/cloud/profitbricks/profitbricks_firewall_rule.py b/cloud/profitbricks/profitbricks_firewall_rule.py index f04d2a7f2b1..b28b6a357b2 100644 --- a/cloud/profitbricks/profitbricks_firewall_rule.py +++ b/cloud/profitbricks/profitbricks_firewall_rule.py @@ -20,7 +20,7 @@ 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" +version_added: "2.2" options: datacenter: description: @@ -45,7 +45,7 @@ options: 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. + - Only traffic originating from the respective MAC address is allowed. No value allows all source MAC addresses. required: false source_ip: description: From 88dd7fd2503281c85890a68d3100594664fd7bf2 Mon Sep 17 00:00:00 2001 From: Ethan Devenport Date: Mon, 29 Aug 2016 06:50:35 +0000 Subject: [PATCH 07/10] Added RETURN documentation. --- .../profitbricks_firewall_rule.py | 59 ++++++++++++++++++- 1 file changed, 56 insertions(+), 3 deletions(-) diff --git a/cloud/profitbricks/profitbricks_firewall_rule.py b/cloud/profitbricks/profitbricks_firewall_rule.py index b28b6a357b2..1fd5a3a8404 100644 --- a/cloud/profitbricks/profitbricks_firewall_rule.py +++ b/cloud/profitbricks/profitbricks_firewall_rule.py @@ -43,7 +43,7 @@ options: - The protocol for the firewall rule. choices: [ "TCP", "UDP", "ICMP" ] required: true - mac_source: + source_mac: description: - Only traffic originating from the respective MAC address is allowed. No value allows all source MAC addresses. required: false @@ -101,7 +101,6 @@ author: Ethan Devenport (ethand@stackpointcloud.com) ''' EXAMPLES = ''' - # Create a firewall rule - name: Create SSH firewall rule profitbricks_firewall_rule: @@ -137,6 +136,60 @@ EXAMPLES = ''' state: absent ''' +RETURN = ''' +--- +id: + description: UUID of the firewall rule. + returned: success + type: string + sample: be60aa97-d9c7-4c22-bebe-f5df7d6b675d +name: + description: Name of the firwall rule. + returned: success + type: string + sample: Allow SSH +protocol: + description: Protocol of the firewall rule. + returned: success + type: string + sample: TCP +source_mac: + description: MAC address of the firewall rule. + returned: success + type: string + sample: 02:01:97:d7:ed:49 +source_ip: + description: Source IP of the firewall rule. + returned: success + type: string + sample: tcp +target_ip: + description: Target IP of the firewal rule. + returned: success + type: string + sample: 10.0.0.1 +port_range_start: + description: Start port of the firewall rule. + returned: success + type: int + sample: 80 +port_range_end: + description: End port of the firewall rule. + returned: success + type: int + sample: 80 +icmp_type: + description: ICMP type of the firewall rule. + returned: success + type: int + sample: 8 +icmp_code: + description: ICMP code of the firewall rule. + returned: success + type: int + sample: 0 +''' + # import uuid import time @@ -218,7 +271,7 @@ def create_firewall_rule(module, profitbricks): f = FirewallRule( name=name, protocol=protocol, - mac_source=source_mac, + source_mac=source_mac, source_ip=source_ip, target_ip=target_ip, port_range_start=port_range_start, From e9fd5ad5dc5a72ae9255fcff0c65f3653462d13a Mon Sep 17 00:00:00 2001 From: Ethan Devenport Date: Mon, 29 Aug 2016 19:45:57 +0000 Subject: [PATCH 08/10] Reverting recent commits back to initial PR and will move the new profitbricks_firewall_rule module and other recent changes to a new branch. Revert "Added support for firewall rules, consolidated resource UUID retrieval methods for server and NIC modules, and set LAN type to int." This reverts commit 464cbb89f225578386a830624633a55e39054544. --- cloud/profitbricks/profitbricks.py | 65 +-- .../profitbricks_firewall_rule.py | 399 ------------------ cloud/profitbricks/profitbricks_nic.py | 105 +++-- 3 files changed, 96 insertions(+), 473 deletions(-) delete mode 100644 cloud/profitbricks/profitbricks_firewall_rule.py diff --git a/cloud/profitbricks/profitbricks.py b/cloud/profitbricks/profitbricks.py index f3016fd13ca..b0791f37e3a 100644 --- a/cloud/profitbricks/profitbricks.py +++ b/cloud/profitbricks/profitbricks.py @@ -98,11 +98,6 @@ options: - This will assign the machine to the public LAN. If no LAN exists with public Internet access it is created. required: false default: false - nic_name: - description: - - The name of the default NIC. - required: false - version_added: '2.2' lan: description: - The ID of the LAN you wish to add the servers to. @@ -115,7 +110,7 @@ options: default: null subscription_password: description: - - The ProfitBricks password. Overrides the PB_PASSWORD environement variable. + - THe ProfitBricks password. Overrides the PB_PASSWORD environement variable. required: false default: null wait: @@ -200,15 +195,14 @@ 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 @@ -216,6 +210,9 @@ 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 @@ -226,9 +223,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.') @@ -248,7 +245,6 @@ 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') @@ -288,7 +284,6 @@ def _create_machine(module, profitbricks, datacenter, name): bus=bus) n = NIC( - name=nic_name, lan=int(lan) ) @@ -316,7 +311,6 @@ 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 @@ -379,7 +373,7 @@ def create_virtual_machine(module, profitbricks): # Locate UUID for datacenter if referenced by name. datacenter_list = profitbricks.list_datacenters() - datacenter_id = _get_resource_id(datacenter_list, datacenter) + datacenter_id = _get_datacenter_id(datacenter_list, datacenter) if datacenter_id: datacenter_found = True @@ -415,13 +409,14 @@ 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_resource_id(server_list, name): + if _get_server_id(server_list, name): continue create_response = _create_machine(module, profitbricks, str(datacenter_id), name) - for nic in create_response['entities']['nics']['items']: - if lan == nic['properties']['lan']: - create_response.update({'public_ip': nic['properties']['ips'][0]}) + 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]}) virtual_machines.append(create_response) @@ -463,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_resource_id(datacenter_list, datacenter) + datacenter_id = _get_datacenter_id(datacenter_list, datacenter) if not datacenter_id: module.fail_json(msg='Virtual data center \'%s\' not found.' % str(datacenter)) @@ -471,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_resource_id(server_list, instance) + server_id = _get_server_id(server_list, instance) if server_id: # Remove the server's boot volume if remove_boot_volume: @@ -522,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_resource_id(datacenter_list, datacenter) + datacenter_id = _get_datacenter_id(datacenter_list, datacenter) if not datacenter_id: module.fail_json(msg='Virtual data center \'%s\' not found.' % str(datacenter)) @@ -530,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_resource_id(server_list, instance) + server_id = _get_server_id(server_list, instance) if server_id: _startstop_machine(module, profitbricks, datacenter_id, server_id) changed = True @@ -559,14 +554,23 @@ def startstop_machine(module, profitbricks, state): return (changed) -def _get_resource_id(resources, identity): +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): """ - Fetch and return the UUID of a resource regardless of whether the name or - UUID is passed. + Fetch and return server UUID by server name if found. """ - for resource in resources['items']: - if identity in (resource['properties']['name'], resource['id']): - return resource['id'] + for server in servers['items']: + if identity in (server['properties']['name'], server['id']): + return server['id'] return None @@ -584,8 +588,7 @@ def main(): image_password=dict(default=None), ssh_keys=dict(type='list', default=[]), bus=dict(default='VIRTIO'), - nic_name=dict(default=str(uuid.uuid4()).replace('-', '')[:10]), - lan=dict(type='int', default=1), + lan=dict(default=1), count=dict(type='int', default=1), auto_increment=dict(type='bool', default=True), instance_ids=dict(type='list', default=[]), diff --git a/cloud/profitbricks/profitbricks_firewall_rule.py b/cloud/profitbricks/profitbricks_firewall_rule.py deleted file mode 100644 index 1fd5a3a8404..00000000000 --- a/cloud/profitbricks/profitbricks_firewall_rule.py +++ /dev/null @@ -1,399 +0,0 @@ -#!/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.2" -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 - source_mac: - description: - - Only traffic originating from the respective MAC address is allowed. 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 -''' - -RETURN = ''' ---- -id: - description: UUID of the firewall rule. - returned: success - type: string - sample: be60aa97-d9c7-4c22-bebe-f5df7d6b675d -name: - description: Name of the firwall rule. - returned: success - type: string - sample: Allow SSH -protocol: - description: Protocol of the firewall rule. - returned: success - type: string - sample: TCP -source_mac: - description: MAC address of the firewall rule. - returned: success - type: string - sample: 02:01:97:d7:ed:49 -source_ip: - description: Source IP of the firewall rule. - returned: success - type: string - sample: tcp -target_ip: - description: Target IP of the firewal rule. - returned: success - type: string - sample: 10.0.0.1 -port_range_start: - description: Start port of the firewall rule. - returned: success - type: int - sample: 80 -port_range_end: - description: End port of the firewall rule. - returned: success - type: int - sample: 80 -icmp_type: - description: ICMP type of the firewall rule. - returned: success - type: int - sample: 8 -icmp_code: - description: ICMP code of the firewall rule. - returned: success - type: int - sample: 0 -''' - -# 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, - source_mac=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/cloud/profitbricks/profitbricks_nic.py b/cloud/profitbricks/profitbricks_nic.py index d13554cf43a..902d5266843 100644 --- a/cloud/profitbricks/profitbricks_nic.py +++ b/cloud/profitbricks/profitbricks_nic.py @@ -84,18 +84,23 @@ 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, FirewallRule + from profitbricks.client import ProfitBricksService, NIC 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 @@ -106,9 +111,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.') @@ -118,7 +123,6 @@ def _wait_for_completion(profitbricks, promise, wait_timeout, msg): promise['requestId'] ) + '" to complete.') - def create_nic(module, profitbricks): """ Creates a NIC. @@ -137,22 +141,28 @@ def create_nic(module, profitbricks): wait_timeout = module.params.get('wait_timeout') # Locate UUID for 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)) + 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 # Locate UUID for Server - server_list = profitbricks.list_servers(datacenter_id) - server_id = _get_resource_id(server_list, server) - - n = NIC( - name=name, - lan=lan - ) - + 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: - nic_response = profitbricks.create_nic(datacenter_id, server_id, n) + n = NIC( + name=name, + lan=lan + ) + + nic_response = profitbricks.create_nic(datacenter, server, n) if wait: _wait_for_completion(profitbricks, nic_response, @@ -163,7 +173,6 @@ 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 @@ -179,44 +188,53 @@ def delete_nic(module, profitbricks): name = module.params.get('name') # Locate UUID for 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)) + 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 # Locate UUID for Server - server_list = profitbricks.list_servers(datacenter_id) - server_id = _get_resource_id(server_list, 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 # Locate UUID for NIC - nic_list = profitbricks.list_nics(datacenter_id, server_id) - nic_id = _get_resource_id(nic_list, name) + 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 try: - nic_response = profitbricks.delete_nic(datacenter_id, server_id, nic_id) + nic_response = profitbricks.delete_nic(datacenter, server, name) 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(type='int'), + name=dict(default=str(uuid.uuid4()).replace('-','')[:10]), + lan=dict(), subscription_user=dict(), subscription_password=dict(), wait=dict(type='bool', default=True), @@ -237,6 +255,7 @@ 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') @@ -262,10 +281,10 @@ def main(): try: (nic_dict) = create_nic(module, profitbricks) - module.exit_json(nic=nic_dict) + module.exit_json(nics=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() +main() \ No newline at end of file From 86285e882497c2ba21b39b71c9305e53853630bc Mon Sep 17 00:00:00 2001 From: Ethan Devenport Date: Wed, 31 Aug 2016 06:24:55 +0000 Subject: [PATCH 09/10] Set variable types, defined choices, and cleaned up whitespace. --- cloud/profitbricks/profitbricks.py | 13 +++++----- cloud/profitbricks/profitbricks_volume.py | 30 ++++++++++++++--------- 2 files changed, 25 insertions(+), 18 deletions(-) diff --git a/cloud/profitbricks/profitbricks.py b/cloud/profitbricks/profitbricks.py index b0791f37e3a..a0bc79c77f8 100644 --- a/cloud/profitbricks/profitbricks.py +++ b/cloud/profitbricks/profitbricks.py @@ -580,15 +580,16 @@ def main(): datacenter=dict(), name=dict(), image=dict(), - cores=dict(default=2), - ram=dict(default=2048), - cpu_family=dict(default='AMD_OPTERON'), - volume_size=dict(default=10), - disk_type=dict(default='HDD'), + cores=dict(type='int', default=2), + ram=dict(type='int', default=2048), + cpu_family=dict(choices=['AMD_OPTERON', 'INTEL_XEON'], + default='AMD_OPTERON'), + volume_size=dict(type='int', default=10), + disk_type=dict(choices=['HDD', 'SSD'], default='HDD'), image_password=dict(default=None), ssh_keys=dict(type='list', default=[]), bus=dict(default='VIRTIO'), - lan=dict(default=1), + 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/cloud/profitbricks/profitbricks_volume.py b/cloud/profitbricks/profitbricks_volume.py index 6b7877f31cc..25b7f771814 100644 --- a/cloud/profitbricks/profitbricks_volume.py +++ b/cloud/profitbricks/profitbricks_volume.py @@ -41,7 +41,7 @@ options: required: false default: VIRTIO choices: [ "IDE", "VIRTIO"] - image: + image: description: - The system image ID for the volume, e.g. a3eae284-a2fe-11e4-b187-5f1f641608c8. This can also be a snapshot image ID. required: true @@ -63,13 +63,13 @@ options: choices: [ "HDD", "SSD" ] licence_type: description: - - The licence type for the volume. This is used when the image is non-standard. + - The licence type for the volume. This is used when the image is non-standard. required: false default: UNKNOWN choices: ["LINUX", "WINDOWS", "UNKNOWN" , "OTHER"] count: description: - - The number of volumes you wish to create. + - The number of volumes you wish to create. required: false default: 1 auto_increment: @@ -170,6 +170,7 @@ def _wait_for_completion(profitbricks, promise, wait_timeout, msg): promise['requestId'] ) + '" to complete.') + def _create_volume(module, profitbricks, datacenter, name): size = module.params.get('size') bus = module.params.get('bus') @@ -201,20 +202,22 @@ def _create_volume(module, profitbricks, datacenter, name): except Exception as e: module.fail_json(msg="failed to create the volume: %s" % str(e)) - + return volume_response + def _delete_volume(module, profitbricks, datacenter, volume): try: profitbricks.delete_volume(datacenter, volume) except Exception as e: module.fail_json(msg="failed to remove the volume: %s" % str(e)) + def create_volume(module, profitbricks): """ Creates a volume. - This will create a volume in a datacenter. + This will create a volume in a datacenter. module : AnsibleModule object profitbricks: authenticated profitbricks object. @@ -256,7 +259,7 @@ def create_volume(module, profitbricks): else: module.fail_json(msg=e.message) - number_range = xrange(count_offset,count_offset + count + len(numbers)) + number_range = xrange(count_offset, count_offset + count + len(numbers)) available_numbers = list(set(number_range).difference(numbers)) names = [] numbers_to_use = available_numbers[:count] @@ -265,7 +268,7 @@ def create_volume(module, profitbricks): else: names = [name] * count - for name in names: + for name in names: create_response = _create_volume(module, profitbricks, str(datacenter), name) volumes.append(create_response) _attach_volume(module, profitbricks, datacenter, create_response['id']) @@ -282,11 +285,12 @@ def create_volume(module, profitbricks): return results + def delete_volume(module, profitbricks): """ Removes a volume. - This will create a volume in a datacenter. + This will create a volume in a datacenter. module : AnsibleModule object profitbricks: authenticated profitbricks object. @@ -324,6 +328,7 @@ def delete_volume(module, profitbricks): return changed + def _attach_volume(module, profitbricks, datacenter, volume): """ Attaches a volume. @@ -339,12 +344,12 @@ def _attach_volume(module, profitbricks, datacenter, volume): server = module.params.get('server') # Locate UUID for Server - if server: + if 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'] + server = s['id'] break try: @@ -352,18 +357,19 @@ def _attach_volume(module, profitbricks, datacenter, volume): except Exception as e: module.fail_json(msg='failed to attach volume: %s' % str(e)) + def main(): module = AnsibleModule( argument_spec=dict( datacenter=dict(), server=dict(), name=dict(), - size=dict(default=10), + size=dict(type='int', default=10), bus=dict(default='VIRTIO'), image=dict(), image_password=dict(default=None), ssh_keys=dict(type='list', default=[]), - disk_type=dict(default='HDD'), + disk_type=dict(choices=['HDD', 'SSD'], default='HDD'), licence_type=dict(default='UNKNOWN'), count=dict(type='int', default=1), auto_increment=dict(type='bool', default=True), From 1eb5745f4978be031917df3aed04822803c35dd8 Mon Sep 17 00:00:00 2001 From: Ethan Devenport Date: Wed, 31 Aug 2016 07:46:12 +0000 Subject: [PATCH 10/10] Added parameter choices for bus. --- cloud/profitbricks/profitbricks.py | 2 +- cloud/profitbricks/profitbricks_volume.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cloud/profitbricks/profitbricks.py b/cloud/profitbricks/profitbricks.py index a0bc79c77f8..7c9f23f6bb0 100644 --- a/cloud/profitbricks/profitbricks.py +++ b/cloud/profitbricks/profitbricks.py @@ -588,7 +588,7 @@ def main(): disk_type=dict(choices=['HDD', 'SSD'], default='HDD'), image_password=dict(default=None), ssh_keys=dict(type='list', default=[]), - bus=dict(default='VIRTIO'), + bus=dict(choices=['VIRTIO', 'IDE'], default='VIRTIO'), lan=dict(type='int', default=1), count=dict(type='int', default=1), auto_increment=dict(type='bool', default=True), diff --git a/cloud/profitbricks/profitbricks_volume.py b/cloud/profitbricks/profitbricks_volume.py index 25b7f771814..1cee9676750 100644 --- a/cloud/profitbricks/profitbricks_volume.py +++ b/cloud/profitbricks/profitbricks_volume.py @@ -365,7 +365,7 @@ def main(): server=dict(), name=dict(), size=dict(type='int', default=10), - bus=dict(default='VIRTIO'), + bus=dict(choices=['VIRTIO', 'IDE'], default='VIRTIO'), image=dict(), image_password=dict(default=None), ssh_keys=dict(type='list', default=[]),