Skip to content
This repository has been archived by the owner on Nov 5, 2019. It is now read-only.

Commit

Permalink
Move compute engine metadata interface into a separate module (#520)
Browse files Browse the repository at this point in the history
  • Loading branch information
elibixby authored and Jon Wayne Parrott committed Jun 10, 2016
1 parent 54d7dce commit c82816c
Show file tree
Hide file tree
Showing 4 changed files with 263 additions and 226 deletions.
126 changes: 126 additions & 0 deletions oauth2client/contrib/_metadata.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
# Copyright 2016 Google Inc. All rights reserved.
#
# 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.

"""Provides helper methods for talking to the Compute Engine metadata server.
See https://cloud.google.com/compute/docs/metadata
"""

import datetime
import httplib2
import json

from six.moves import http_client
from six.moves.urllib import parse as urlparse

from oauth2client._helpers import _from_bytes
from oauth2client.client import _UTCNOW
from oauth2client import util


METADATA_ROOT = 'http://metadata.google.internal/computeMetadata/v1/'
METADATA_HEADERS = {'Metadata-Flavor': 'Google'}


def get(path, http_request=None, root=METADATA_ROOT, recursive=None):
"""Fetch a resource from the metadata server.
Args:
path: A string indicating the resource to retrieve. For example,
'instance/service-accounts/defualt'
http_request: A callable that matches the method
signature of httplib2.Http.request. Used to make the request to the
metadataserver.
root: A string indicating the full path to the metadata server root.
recursive: A boolean indicating whether to do a recursive query of
metadata. See
https://cloud.google.com/compute/docs/metadata#aggcontents
Returns:
A dictionary if the metadata server returns JSON, otherwise a string.
Raises:
httplib2.Httplib2Error if an error corrured while retrieving metadata.
"""
if not http_request:
http_request = httplib2.Http().request

url = urlparse.urljoin(root, path)
url = util._add_query_parameter(url, 'recursive', recursive)

response, content = http_request(
url,
headers=METADATA_HEADERS
)

if response.status == http_client.OK:
decoded = _from_bytes(content)
if response['content-type'] == 'application/json':
return json.loads(decoded)
else:
return decoded
else:
raise httplib2.HttpLib2Error(
'Failed to retrieve {0} from the Google Compute Engine'
'metadata service. Response:\n{1}'.format(url, response))


def get_service_account_info(service_account='default', http_request=None):
"""Get information about a service account from the metadata server.
Args:
service_account: An email specifying the service account for which to
look up information. Default will be information for the "default"
service account of the current compute engine instance.
http_request: A callable that matches the method
signature of httplib2.Http.request. Used to make the request to the
metadata server.
Returns:
A dictionary with information about the specified service account,
for example:
{
'email': '...',
'scopes': ['scope', ...],
'aliases': ['default', '...']
}
"""
return get(
'instance/service-accounts/{0}'.format(service_account),
recursive=True,
http_request=http_request)


def get_token(service_account='default', http_request=None):
"""Fetch an oauth token for the
Args:
service_account: An email specifying the service account this token
should represent. Default will be a token for the "default" service
account of the current compute engine instance.
http_request: A callable that matches the method
signature of httplib2.Http.request. Used to make the request to the
metadataserver.
Returns:
A tuple of (access token, token expiration), where access token is the
access token as a string and token expiration is a datetime object
that indicates when the access token will expire.
"""
token_json = get(
'instance/service-accounts/{0}/token'.format(service_account),
http_request=http_request)
token_expiry = _UTCNOW() + datetime.timedelta(
seconds=token_json['expires_in'])
return token_json['access_token'], token_expiry
68 changes: 11 additions & 57 deletions oauth2client/contrib/gce.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,30 +17,23 @@
Utilities for making it easier to use OAuth 2.0 on Google Compute Engine.
"""

import datetime
import json
import logging
import warnings

import httplib2
from six.moves import http_client
from six.moves import urllib

from oauth2client._helpers import _from_bytes
from oauth2client import util
from oauth2client.client import HttpAccessTokenRefreshError
from oauth2client.client import AssertionCredentials
from oauth2client.client import HttpAccessTokenRefreshError
from oauth2client.contrib import _metadata


__author__ = '[email protected] (Joe Gregorio)'

logger = logging.getLogger(__name__)

# URI Template for the endpoint that returns access_tokens.
_METADATA_ROOT = ('http://metadata.google.internal/computeMetadata/v1/'
'instance/service-accounts/default/')
META = _METADATA_ROOT + 'token'
_DEFAULT_EMAIL_METADATA = _METADATA_ROOT + 'email'
_SCOPES_WARNING = """\
You have requested explicit scopes to be used with a GCE service account.
Using this argument will have no effect on the actual scopes for tokens
Expand All @@ -49,30 +42,6 @@
"""


def _get_service_account_email(http_request=None):
"""Get the GCE service account email from the current environment.
Args:
http_request: callable, (Optional) a callable that matches the method
signature of httplib2.Http.request, used to make
the request to the metadata service.
Returns:
tuple, A pair where the first entry is an optional response (from a
failed request) and the second is service account email found (as
a string).
"""
if http_request is None:
http_request = httplib2.Http().request
response, content = http_request(
_DEFAULT_EMAIL_METADATA, headers={'Metadata-Flavor': 'Google'})
if response.status == http_client.OK:
content = _from_bytes(content)
return None, content
else:
return response, content


class AppAssertionCredentials(AssertionCredentials):
"""Credentials object for Compute Engine Assertion Grants
Expand Down Expand Up @@ -106,6 +75,8 @@ def __init__(self, scope='', **kwargs):
# Assertion type is no longer used, but still in the
# parent class signature.
super(AppAssertionCredentials, self).__init__(None)

# Cache until Metadata Server supports Cache-Control Header
self._service_account_email = None

@classmethod
Expand All @@ -126,23 +97,11 @@ def _refresh(self, http_request):
Raises:
HttpAccessTokenRefreshError: When the refresh fails.
"""
response, content = http_request(
META, headers={'Metadata-Flavor': 'Google'})
content = _from_bytes(content)
if response.status == http_client.OK:
try:
token_content = json.loads(content)
except Exception as e:
raise HttpAccessTokenRefreshError(str(e),
status=response.status)
self.access_token = token_content['access_token']
delta = datetime.timedelta(seconds=int(token_content['expires_in']))
self.token_expiry = delta + datetime.datetime.utcnow()
else:
if response.status == http_client.NOT_FOUND:
content += (' This can occur if a VM was created'
' with no service account or scopes.')
raise HttpAccessTokenRefreshError(content, status=response.status)
try:
self.access_token, self.token_expiry = _metadata.get_token(
http_request=http_request)
except httplib2.HttpLib2Error as e:
raise HttpAccessTokenRefreshError(str(e))

@property
def serialization_data(self):
Expand Down Expand Up @@ -187,11 +146,6 @@ def service_account_email(self):
Compute Engine metadata service.
"""
if self._service_account_email is None:
failure, email = _get_service_account_email()
if failure is None:
self._service_account_email = email
else:
raise AttributeError('Failed to retrieve the email from the '
'Google Compute Engine metadata service',
failure, email)
self._service_account_email = (
_metadata.get_service_account_info()['email'])
return self._service_account_email
Loading

0 comments on commit c82816c

Please sign in to comment.