Source code for gordon_gcp.clients.gce
# -*- coding: utf-8 -*-
#
# Copyright 2018 Spotify AB
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
Client classes to retrieve project and instance data from GCE.
These clients use the asynchronous HTTP client defined in
:class:`.AIOConnection` and require service account or JWT-token
credentials for authentication.
To use:
.. code-block:: python
import asyncio
import aiohttp
import gordon_gcp
loop = asyncio.get_event_loop()
async def main():
session = aiohttp.ClientSession()
auth_client = gordon_gcp.GAuthClient(
keyfile='/path/to/keyfile', session=session)
client = gordon_gcp.GCEClient(auth_client, session)
instances = await client.list_instances('project-id')
print(instances)
loop.run_until_complete(main())
# example output
# [{'hostname': 'instance-1', 'internal_ip': '10.10.10.10',
# 'external_ip': '192.168.1.10'}]
"""
import logging
from gordon_gcp.clients import _utils
from gordon_gcp.clients import http
__all__ = ('GCEClient',)
[docs]class GCEClient(http.AIOConnection, _utils.GPaginatorMixin):
"""Async client to interact with Google Cloud Compute API.
Attributes:
BASE_URL (str): base compute endpoint URL.
Args:
auth_client (.GAuthClient): client to manage authentication for
HTTP API requests.
session (aiohttp.ClientSession): (optional) ``aiohttp`` HTTP
session to use for sending requests. Defaults to the
session object attached to :obj:`auth_client` if not
provided.
api_version (str): version of API endpoint to send requests to.
blacklisted_tags (list): Do not collect an instance if it has
been tagged with any of these.
blacklisted_metadata (list): Do not collect an instance if its
metadata key:val matches a {key:val} dict in this list.
"""
BASE_URL = 'https://www.googleapis.com/compute/'
def __init__(self,
auth_client=None,
session=None,
api_version='v1',
blacklisted_tags=None,
blacklisted_metadata=None):
super().__init__(auth_client=auth_client, session=session)
self.api_version = api_version
self.blacklisted_tags = blacklisted_tags or []
self.blacklisted_metadata = blacklisted_metadata or []
[docs] async def list_instances(self,
project,
page_size=100,
instance_filter=None):
"""Fetch all instances in a GCE project.
You can find the endpoint documentation `here <https://cloud.
google.com/compute/docs/reference/latest/instances/
aggregatedList>`__.
Args:
project (str): unique, user-provided project ID.
page_size (int): hint for the client to only retrieve up to
this number of results per API call.
instance_filter (str): endpoint-specific filter string used
to retrieve a subset of instances. This is passed
directly to the endpoint's "filter" URL query parameter.
Returns:
list(dicts): data of all instances in the given
:obj:`project`
"""
url = (f'{self.BASE_URL}{self.api_version}/projects/{project}'
'/aggregated/instances')
params = {'maxResults': page_size}
if instance_filter:
params['filter'] = instance_filter
responses = await self.list_all(url, params)
instances = self._parse_rsps_for_instances(responses)
return instances
def _parse_rsps_for_instances(self, responses):
instances = []
for response in responses:
for zone in response.get('items', {}).values():
instances.extend(self._filter_zone_instances(zone))
return instances
def _filter_zone_instances(self, zone):
instances = []
for instance in zone.get('instances', []):
if not any([
self._blacklisted_by_tag(instance),
self._blacklisted_by_metadata(instance)
]):
instances.append(instance)
return instances
def _blacklisted_by_tag(self, instance):
instance_tags = instance.get('tags', {}).get('items', [])
for tag in instance_tags:
if tag in self.blacklisted_tags:
msg = (f'Instance "{instance["name"]}" filtered out for '
f'blacklisted tag: "{tag}"')
logging.debug(msg)
return True
return False
def _blacklisted_by_metadata(self, instance):
# NOTE: Both key and value are used when comparing the instance and
# blacklist metadata.
instance_metadata = instance.get('metadata', {}).get('items', [])
for metadata in instance_metadata:
for bl_meta in self.blacklisted_metadata:
if bl_meta.get(metadata['key']) == metadata['value']:
msg = (f'Instance "{instance["name"]}" filtered out for '
f'blacklisted metadata: "{bl_meta}"')
logging.debug(msg)
return True
return False