From 92a12a635a5553709535ed260987ceaf0a52143d Mon Sep 17 00:00:00 2001 From: Jack Ivanov Date: Fri, 16 Aug 2019 17:03:04 +0200 Subject: [PATCH] GCP: Refactoring, remove deprecated modules --- library/gce_region_facts.py | 139 --------------------------- library/gcp_compute_location_info.py | 93 ++++++++++++++++++ roles/cloud-gce/tasks/main.yml | 125 ++++++++++++++---------- roles/cloud-gce/tasks/prompts.yml | 44 ++++++--- roles/cloud-gce/tasks/venv.yml | 11 +-- 5 files changed, 197 insertions(+), 215 deletions(-) delete mode 100644 library/gce_region_facts.py create mode 100644 library/gcp_compute_location_info.py diff --git a/library/gce_region_facts.py b/library/gce_region_facts.py deleted file mode 100644 index 65acfb6..0000000 --- a/library/gce_region_facts.py +++ /dev/null @@ -1,139 +0,0 @@ -#!/usr/bin/python -# Copyright 2013 Google Inc. -# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) - -from __future__ import absolute_import, division, print_function -__metaclass__ = type - - -ANSIBLE_METADATA = {'metadata_version': '1.1', - 'status': ['preview'], - 'supported_by': 'community'} - - -DOCUMENTATION = ''' ---- -module: gce_region_facts -version_added: "5.3" -short_description: Gather facts about GCE regions. -description: - - Gather facts about GCE regions. -options: - service_account_email: - version_added: "1.6" - description: - - service account email - required: false - default: null - aliases: [] - pem_file: - version_added: "1.6" - description: - - path to the pem file associated with the service account email - This option is deprecated. Use 'credentials_file'. - required: false - default: null - aliases: [] - credentials_file: - version_added: "2.1.0" - description: - - path to the JSON file associated with the service account email - required: false - default: null - aliases: [] - project_id: - version_added: "1.6" - description: - - your GCE project ID - required: false - default: null - aliases: [] - requirements: - - "python >= 2.6" - - "apache-libcloud >= 0.13.3, >= 0.17.0 if using JSON credentials" -author: "Jack Ivanov (@jackivanov)" -''' - -EXAMPLES = ''' -# Gather facts about all regions -- gce_region_facts: -''' - -RETURN = ''' -regions: - returned: on success - description: > - Each element consists of a dict with all the information related - to that region. - type: list - sample: "[{ - "name": "asia-east1", - "status": "UP", - "zones": [ - { - "name": "asia-east1-a", - "status": "UP" - }, - { - "name": "asia-east1-b", - "status": "UP" - }, - { - "name": "asia-east1-c", - "status": "UP" - } - ] - }]" -''' -try: - from libcloud.compute.types import Provider - from libcloud.compute.providers import get_driver - from libcloud.common.google import GoogleBaseError, QuotaExceededError, ResourceExistsError, ResourceNotFoundError - _ = Provider.GCE - HAS_LIBCLOUD = True -except ImportError: - HAS_LIBCLOUD = False - -from ansible.module_utils.basic import AnsibleModule -from ansible.module_utils.gce import gce_connect, unexpected_error_msg - - -def main(): - module = AnsibleModule( - argument_spec=dict( - service_account_email=dict(), - pem_file=dict(type='path'), - credentials_file=dict(type='path'), - project_id=dict(), - ) - ) - - if not HAS_LIBCLOUD: - module.fail_json(msg='libcloud with GCE support (0.17.0+) required for this module') - - gce = gce_connect(module) - - changed = False - gce_regions = [] - - try: - regions = gce.ex_list_regions() - for r in regions: - gce_region = {} - gce_region['name'] = r.name - gce_region['status'] = r.status - gce_region['zones'] = [] - for z in r.zones: - gce_zone = {} - gce_zone['name'] = z.name - gce_zone['status'] = z.status - gce_region['zones'].append(gce_zone) - gce_regions.append(gce_region) - json_output = { 'regions': gce_regions } - module.exit_json(changed=False, results=json_output) - except ResourceNotFoundError: - pass - - -if __name__ == '__main__': - main() diff --git a/library/gcp_compute_location_info.py b/library/gcp_compute_location_info.py new file mode 100644 index 0000000..aa276a9 --- /dev/null +++ b/library/gcp_compute_location_info.py @@ -0,0 +1,93 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +################################################################################ +# Documentation +################################################################################ + +ANSIBLE_METADATA = {'metadata_version': '1.1', 'status': ["preview"], 'supported_by': 'community'} + +################################################################################ +# Imports +################################################################################ +from ansible.module_utils.gcp_utils import navigate_hash, GcpSession, GcpModule, GcpRequest +import json + +################################################################################ +# Main +################################################################################ + + +def main(): + module = GcpModule(argument_spec=dict(filters=dict(type='list', elements='str'), scope=dict(required=True, type='str'))) + + if module._name == 'gcp_compute_image_facts': + module.deprecate("The 'gcp_compute_image_facts' module has been renamed to 'gcp_compute_regions_info'", version='2.13') + + if not module.params['scopes']: + module.params['scopes'] = ['https://www.googleapis.com/auth/compute'] + + items = fetch_list(module, collection(module), query_options(module.params['filters'])) + if items.get('items'): + items = items.get('items') + else: + items = [] + return_value = {'resources': items} + module.exit_json(**return_value) + + +def collection(module): + return "https://www.googleapis.com/compute/v1/projects/{project}/{scope}".format(**module.params) + + +def fetch_list(module, link, query): + auth = GcpSession(module, 'compute') + response = auth.get(link, params={'filter': query}) + return return_if_object(module, response) + + +def query_options(filters): + if not filters: + return '' + + if len(filters) == 1: + return filters[0] + else: + queries = [] + for f in filters: + # For multiple queries, all queries should have () + if f[0] != '(' and f[-1] != ')': + queries.append("(%s)" % ''.join(f)) + else: + queries.append(f) + + return ' '.join(queries) + + +def return_if_object(module, response): + # If not found, return nothing. + if response.status_code == 404: + return None + + # If no content, return nothing. + if response.status_code == 204: + return None + + try: + module.raise_for_status(response) + result = response.json() + except getattr(json.decoder, 'JSONDecodeError', ValueError) as inst: + module.fail_json(msg="Invalid JSON response with error: %s" % inst) + + if navigate_hash(result, ['error', 'errors']): + module.fail_json(msg=navigate_hash(result, ['error', 'errors'])) + + return result + + +if __name__ == "__main__": + main() diff --git a/roles/cloud-gce/tasks/main.yml b/roles/cloud-gce/tasks/main.yml index 1db619b..cd50fc6 100644 --- a/roles/cloud-gce/tasks/main.yml +++ b/roles/cloud-gce/tasks/main.yml @@ -2,60 +2,83 @@ - name: Build python virtual environment import_tasks: venv.yml +- name: Include prompts + import_tasks: prompts.yml + +- name: Network configured + gcp_compute_network: + auth_kind: serviceaccount + service_account_file: "{{ credentials_file_path }}" + project: "{{ project_id }}" + name: algovpn + auto_create_subnetworks: true + routing_config: + routing_mode: REGIONAL + register: gcp_compute_network + +- name: Firewall configured + gcp_compute_firewall: + auth_kind: serviceaccount + service_account_file: "{{ credentials_file_path }}" + project: "{{ project_id }}" + name: algovpn + network: "{{ gcp_compute_network }}" + direction: INGRESS + allowed: + - ip_protocol: udp + ports: + - '500' + - '4500' + - '{{ wireguard_port|string }}' + - ip_protocol: tcp + ports: + - '22' + - ip_protocol: icmp + - block: - - name: Include prompts - import_tasks: prompts.yml - - - name: Network configured - gce_net: + - name: External IP allocated + gcp_compute_address: + auth_kind: serviceaccount + service_account_file: "{{ credentials_file_path }}" + project: "{{ project_id }}" name: "{{ algo_server_name }}" - fwname: "{{ algo_server_name }}-fw" - allowed: "udp:500,4500,{{ wireguard_port }};tcp:22" - state: "present" - mode: auto - src_range: 0.0.0.0/0 - service_account_email: "{{ service_account_email }}" - credentials_file: "{{ credentials_file_path }}" - project_id: "{{ project_id }}" + region: "{{ algo_region }}" + register: gcp_compute_address - - block: - - name: External IP allocated - gce_eip: - service_account_email: "{{ service_account_email }}" - credentials_file: "{{ credentials_file_path }}" - project_id: "{{ project_id }}" - name: "{{ algo_server_name }}" - region: "{{ algo_region.split('-')[0:2] | join('-') }}" - state: present - register: gce_eip + - name: Set External IP as a fact + set_fact: + external_ip: "{{ gcp_compute_address.address }}" + when: cloud_providers.gce.external_static_ip - - name: Set External IP as a fact - set_fact: - external_ip: "{{ gce_eip.address }}" - when: cloud_providers.gce.external_static_ip - - - name: "Creating a new instance..." - gce: - instance_names: "{{ algo_server_name }}" - zone: "{{ algo_region }}" - external_ip: "{{ external_ip | default('ephemeral') }}" - machine_type: "{{ cloud_providers.gce.size }}" - image: "{{ cloud_providers.gce.image }}" - service_account_email: "{{ service_account_email }}" - credentials_file: "{{ credentials_file_path }}" - project_id: "{{ project_id }}" - metadata: - ssh-keys: "ubuntu:{{ ssh_public_key_lookup }}" - user-data: | - #!/bin/bash - sudo apt-get remove -y --purge sshguard - network: "{{ algo_server_name }}" - tags: +- name: Instance created + gcp_compute_instance: + auth_kind: serviceaccount + service_account_file: "{{ credentials_file_path }}" + project: "{{ project_id }}" + name: "{{ algo_server_name }}" + zone: "{{ algo_zone }}" + machine_type: "{{ cloud_providers.gce.size }}" + disks: + - auto_delete: true + boot: true + initialize_params: + source_image: "projects/ubuntu-os-cloud/global/images/family/{{ cloud_providers.gce.image }}" + metadata: + ssh-keys: "ubuntu:{{ ssh_public_key_lookup }}" + user-data: | + #!/bin/bash + sudo apt-get remove -y --purge sshguard + network_interfaces: + - network: "{{ gcp_compute_network }}" + access_configs: + - name: "{{ algo_server_name }}" + nat_ip: "{{ gcp_compute_address|default(None) }}" + type: ONE_TO_ONE_NAT + tags: + items: - "environment-algo" - register: google_vm + register: gcp_compute_instance - - set_fact: - cloud_instance_ip: "{{ google_vm.instance_data[0].public_ip }}" - ansible_ssh_user: ubuntu - environment: - PYTHONPATH: "{{ gce_venv }}/lib/python2.7/site-packages/" +- set_fact: + cloud_instance_ip: "{{ gcp_compute_instance.networkInterfaces[0].accessConfigs[0].natIP }}" + ansible_ssh_user: ubuntu diff --git a/roles/cloud-gce/tasks/prompts.yml b/roles/cloud-gce/tasks/prompts.yml index dfedecf..b8a3896 100644 --- a/roles/cloud-gce/tasks/prompts.yml +++ b/roles/cloud-gce/tasks/prompts.yml @@ -21,36 +21,32 @@ - block: - name: Get regions - gce_region_facts: - service_account_email: "{{ credentials_file_lookup.client_email }}" - credentials_file: "{{ credentials_file_path }}" - project_id: "{{ credentials_file_lookup.project_id }}" - register: _gce_regions + gcp_compute_location_info: + auth_kind: serviceaccount + service_account_file: "{{ credentials_file_path }}" + project: "{{ project_id }}" + scope: regions + filters: status=UP + register: gcp_compute_regions_info - name: Set facts about the regions set_fact: gce_regions: >- - [{%- for region in _gce_regions.results.regions | sort(attribute='name') -%} - {% if region.status == "UP" %} - {% for zone in region.zones | sort(attribute='name') %} - {% if zone.status == "UP" %} - '{{ zone.name }}' - {% endif %}{% if not loop.last %},{% endif %} - {% endfor %} - {% endif %}{% if not loop.last %},{% endif %} + [{%- for region in gcp_compute_regions_info.resources | sort(attribute='name') -%} + '{{ region.name }}'{% if not loop.last %},{% endif %} {%- endfor -%}] - name: Set facts about the default region set_fact: default_region: >- {% for region in gce_regions %} - {%- if region == "us-east1-b" %}{{ loop.index }}{% endif %} + {%- if region == "us-east1" %}{{ loop.index }}{% endif %} {%- endfor %} - pause: prompt: | What region should the server be located in? - (https://cloud.google.com/compute/docs/regions-zones/) + (https://cloud.google.com/compute/docs/regions-zones/#locations) {% for r in gce_regions %} {{ loop.index }}. {{ r }} {% endfor %} @@ -60,8 +56,24 @@ register: _gce_region when: region is undefined -- set_fact: +- name: Set region as a fact + set_fact: algo_region: >- {% if region is defined %}{{ region }} {%- elif _gce_region.user_input %}{{ gce_regions[_gce_region.user_input | int -1 ] }} {%- else %}{{ gce_regions[default_region | int - 1] }}{% endif %} + +- name: Get zones + gcp_compute_location_info: + auth_kind: serviceaccount + service_account_file: "{{ credentials_file_path }}" + project: "{{ project_id }}" + scope: zones + filters: + - "name={{ algo_region }}-*" + - "status=UP" + register: gcp_compute_zone_info + +- name: Set random available zone as a fact + set_fact: + algo_zone: "{{ (gcp_compute_zone_info.resources | random(seed=algo_server_name + algo_region + project_id) ).name }}" diff --git a/roles/cloud-gce/tasks/venv.yml b/roles/cloud-gce/tasks/venv.yml index b41323f..71be67a 100644 --- a/roles/cloud-gce/tasks/venv.yml +++ b/roles/cloud-gce/tasks/venv.yml @@ -1,14 +1,7 @@ --- -- name: Clean up the environment - file: - dest: "{{ gce_venv }}" - state: absent - when: clean_environment - - name: Install requirements pip: name: - - apache-libcloud + - requests>=2.18.4 + - google-auth>=1.3.0 state: latest - virtualenv: "{{ gce_venv }}" - virtualenv_python: python2.7