fixing gcp inv plugin (#54426)

* start fixing gcp inv plugin

* minor fixes

* added clog

* ajust comments

* link indv zone/project

* separate specific zone/project from other params

* restoring zones query per project

* also work when zones given

* fixed scopes, removed incorrect docs as not option
pull/4420/head
Brian Coca 2019-03-27 12:13:39 -04:00 committed by Adam Miller
parent 8dbdd987d8
commit f9876f3450
2 changed files with 39 additions and 37 deletions

View File

@ -0,0 +1,2 @@
bugfixes:
- gce inventory plugin was misusing the API and needlessly doing late validation.

View File

@ -24,26 +24,35 @@ DOCUMENTATION = '''
choices: ['gcp_compute'] choices: ['gcp_compute']
zones: zones:
description: A list of regions in which to describe GCE instances. description: A list of regions in which to describe GCE instances.
default: all zones available to a given project If none provided, it defaults to all zones available to a given project.
type: list
projects: projects:
description: A list of projects in which to describe GCE instances. description: A list of projects in which to describe GCE instances.
type: list
required: True
filters: filters:
description: > description: >
A list of filter value pairs. Available filters are listed here A list of filter value pairs. Available filters are listed here
U(https://cloud.google.com/compute/docs/reference/rest/v1/instances/list). U(https://cloud.google.com/compute/docs/reference/rest/v1/instances/list).
Each additional filter in the list will act be added as an AND condition Each additional filter in the list will act be added as an AND condition
(filter1 and filter2) (filter1 and filter2)
type: list
hostnames: hostnames:
description: A list of options that describe the ordering for which description: A list of options that describe the ordering for which
hostnames should be assigned. Currently supported hostnames are hostnames should be assigned. Currently supported hostnames are
'public_ip', 'private_ip', or 'name'. 'public_ip', 'private_ip', or 'name'.
default: ['public_ip', 'private_ip', 'name'] default: ['public_ip', 'private_ip', 'name']
type: list
auth_kind: auth_kind:
description: description:
- The type of credential used. - The type of credential used.
required: True
choices: ['application', 'serviceaccount', 'machineaccount']
service_account_file: service_account_file:
description: description:
- The path of a Service Account JSON file if serviceaccount is selected as type. - The path of a Service Account JSON file if serviceaccount is selected as type.
required: True
type: path
service_account_email: service_account_email:
description: description:
- An optional service account email address if machineaccount is selected - An optional service account email address if machineaccount is selected
@ -74,8 +83,6 @@ projects:
filters: filters:
- machineType = n1-standard-1 - machineType = n1-standard-1
- scheduling.automaticRestart = true AND machineType = n1-standard-1 - scheduling.automaticRestart = true AND machineType = n1-standard-1
scopes:
- https://www.googleapis.com/auth/compute
service_account_file: /tmp/service_account.json service_account_file: /tmp/service_account.json
auth_kind: serviceaccount auth_kind: serviceaccount
keyed_groups: keyed_groups:
@ -91,15 +98,15 @@ compose:
ansible_host: networkInterfaces[0].accessConfigs[0].natIP ansible_host: networkInterfaces[0].accessConfigs[0].natIP
''' '''
import json
from ansible.errors import AnsibleError, AnsibleParserError from ansible.errors import AnsibleError, AnsibleParserError
from ansible.module_utils._text import to_native from ansible.module_utils._text import to_native
from ansible.module_utils.gcp_utils import GcpSession, navigate_hash, GcpRequestException from ansible.module_utils.gcp_utils import GcpSession, navigate_hash, GcpRequestException
from ansible.plugins.inventory import BaseInventoryPlugin, Constructable, Cacheable from ansible.plugins.inventory import BaseInventoryPlugin, Constructable, Cacheable
import json
# The mappings give an array of keys to get from the filter name to the value # Mocking a module to reuse module_utils
# returned by boto3's GCE describe_instances method.
class GcpMockModule(object): class GcpMockModule(object):
def __init__(self, params): def __init__(self, params):
self.params = params self.params = params
@ -142,12 +149,12 @@ class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable):
return True return True
return False return False
def self_link(self, params): def self_link(self, project, zone):
''' '''
:param params: a dict containing all of the fields relevant to build URL :param params: a dict containing all of the fields relevant to build URL
:return the formatted URL as a string. :return the formatted URL as a string.
''' '''
return "https://www.googleapis.com/compute/v1/projects/{project}/zones/{zone}/instances".format(**params) return "https://www.googleapis.com/compute/v1/projects/%s/zones/%s/instances" % (project, zone)
def fetch_list(self, params, link, query): def fetch_list(self, params, link, query):
''' '''
@ -161,12 +168,12 @@ class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable):
response = auth.get(link, params={'filter': query}) response = auth.get(link, params={'filter': query})
return self._return_if_object(module, response) return self._return_if_object(module, response)
def _get_zones(self, config_data): def _get_zones(self, project, config_data):
''' '''
:param config_data: dict of info from inventory file :param config_data: dict of info from inventory file
:return an array of zones that this project has access to :return an array of zones that this project has access to
''' '''
link = "https://www.googleapis.com/compute/v1/projects/{project}/zones".format(**config_data) link = "https://www.googleapis.com/compute/v1/projects/%s/zones" % project
zones = [] zones = []
zones_response = self.fetch_list(config_data, link, '') zones_response = self.fetch_list(config_data, link, '')
for item in zones_response['items']: for item in zones_response['items']:
@ -338,27 +345,18 @@ class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable):
if self.get_option('use_contrib_script_compatible_sanitization'): if self.get_option('use_contrib_script_compatible_sanitization'):
self._sanitize_group_name = self._legacy_script_compatible_group_sanitization self._sanitize_group_name = self._legacy_script_compatible_group_sanitization
# get user specifications # setup parameters as expected by 'fake module class' to reuse module_utils w/o changing the API
if 'zones' in config_data: params = {
if not isinstance(config_data['zones'], list): 'filters': self.get_option('filters'),
raise AnsibleParserError("Zones must be a list in GCP inventory YAML files") 'projects': self.get_option('projects'),
'scopes': ['https://www.googleapis.com/auth/compute'],
'zones': self.get_option('zones'),
'auth_kind': self.get_option('auth_kind'),
'service_account_file': self.get_option('service_account_file'),
'service_account_email': self.get_option('service_account_email'),
}
# get user specifications query = self._get_query_options(params['filters'])
if 'projects' not in config_data:
raise AnsibleParserError("Projects must be included in inventory YAML file")
if not isinstance(config_data['projects'], list):
raise AnsibleParserError("Projects must be a list in GCP inventory YAML files")
# add in documented defaults
if 'filters' not in config_data:
config_data['filters'] = None
projects = config_data['projects']
zones = config_data.get('zones')
config_data['scopes'] = ['https://www.googleapis.com/auth/compute']
query = self._get_query_options(config_data['filters'])
# Cache logic # Cache logic
if cache: if cache:
@ -379,15 +377,17 @@ class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable):
if not cache or cache_needs_update: if not cache or cache_needs_update:
cached_data = {} cached_data = {}
for project in projects: for project in params['projects']:
cached_data[project] = {} cached_data[project] = {}
config_data['project'] = project params['project'] = project
if not zones: if not params['zones']:
zones = self._get_zones(config_data) zones = self._get_zones(project, params)
else:
zones = params['zones']
for zone in zones: for zone in zones:
config_data['zone'] = zone link = self.self_link(project, zone)
link = self.self_link(config_data) params['zone'] = zone
resp = self.fetch_list(config_data, link, query) resp = self.fetch_list(params, link, query)
self._add_hosts(resp.get('items'), config_data) self._add_hosts(resp.get('items'), config_data)
cached_data[project][zone] = resp.get('items') cached_data[project][zone] = resp.get('items')