Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix #34, initialized compute engine support. #69

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions gcloud/compute/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
__version__ = '0.1'

# TODO: Allow specific scopes and authorization levels.
SCOPE = ('https://www.googleapis.com/auth/compute')
"""The scope required for authenticating as a Compute Engine consumer."""


def get_connection(project_name, client_email, private_key_path):
from gcloud.credentials import Credentials
from gcloud.compute.connection import Connection

credentials = Credentials.get_for_service_account(
client_email, private_key_path, scope=SCOPE)
return Connection(project_name=project_name, credentials=credentials)
115 changes: 115 additions & 0 deletions gcloud/compute/connection.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import json
import urllib

from gcloud import connection
from gcloud.compute import exceptions
from gcloud.compute.instance import Instance


class Connection(connection.Connection):
"""A connection to the Google Compute Engine via the Protobuf API.

This class should understand only the basic types (and protobufs)
in method arguments, however should be capable of returning advanced types.

:type credentials: :class:`gcloud.credentials.Credentials`
:param credentials: The OAuth2 Credentials to use for this connection.
"""

API_BASE_URL = 'https://www.googleapis.com'
"""The base of the API call URL."""

API_VERSION = 'v1'
"""The version of the API, used in building the API call's URL."""

API_URL_TEMPLATE = ('{api_base_url}/compute/{api_version}/{path}')
"""A template used to craft the URL pointing toward a particular API call."""

_EMPTY = object()
"""A pointer to represent an empty value for default arguments."""

def __init__(self, project_name=None, *args, **kwargs):

super(Connection, self).__init__(*args, **kwargs)

self.project_name = project_name

def build_api_url(self, path, query_params=None, api_base_url=None,
api_version=None):

url = self.API_URL_TEMPLATE.format(
api_base_url=(api_base_url or self.API_BASE_URL),
api_version=(api_version or self.API_VERSION),
path=path)

query_params = query_params or {}
query_params.update({'project': self.project_name})
url += '?' + urllib.urlencode(query_params)

return url

def make_request(self, method, url, data=None, content_type=None,
headers=None):

headers = headers or {}
headers['Accept-Encoding'] = 'gzip'

if data:
content_length = len(str(data))
else:
content_length = 0

headers['Content-Length'] = content_length

if content_type:
headers['Content-Type'] = content_type

return self.http.request(uri=url, method=method, headers=headers,
body=data)

def api_request(self, method, path=None, query_params=None,
data=None, content_type=None,
api_base_url=None, api_version=None,
expect_json=True):

url = self.build_api_url(path=path, query_params=query_params,
api_base_url=api_base_url,
api_version=api_version)

# Making the executive decision that any dictionary
# data will be sent properly as JSON.
if data and isinstance(data, dict):
data = json.dumps(data)
content_type = 'application/json'

response, content = self.make_request(
method=method, url=url, data=data, content_type=content_type)

# TODO: Add better error handling.
if response.status == 404:
raise exceptions.NotFoundError(response, content)
elif not 200 <= response.status < 300:
raise exceptions.ConnectionError(response, content)

if content and expect_json:
# TODO: Better checking on this header for JSON.
content_type = response.get('content-type', '')
if not content_type.startswith('application/json'):
raise TypeError('Expected JSON, got %s' % content_type)
return json.loads(content)

return content

def get_instance(self, instance_name, zone):
instance = self.new_instance(instance_name, zone)
response = self.api_request(method='GET', path=instance.path)
return Instance.from_dict(response, connection=self)

def reset_instance(self, instance):
self.api_request(method='POST', path=instance.path + 'reset')
return True

# TODO: Add instance and error handling.
def new_instance(self, instance, zone):
if isinstance(instance, basestring):
return Instance(connection=self, name=instance, zone=zone)
15 changes: 15 additions & 0 deletions gcloud/compute/demo/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import os
from gcloud import compute


__all__ = ['get_connection', 'CLIENT_EMAIL', 'PRIVATE_KEY_PATH',
'PROJECT_NAME']


CLIENT_EMAIL = '524635209885-rda26ks46309o10e0nc8rb7d33rn0hlm@developer.gserviceaccount.com'
PRIVATE_KEY_PATH = os.path.join(os.path.dirname(__file__), 'demo.key')
PROJECT_NAME = 'gceremote'


def get_connection():
return compute.get_connection(PROJECT_NAME, CLIENT_EMAIL, PRIVATE_KEY_PATH)
Binary file added gcloud/compute/demo/demo.key
Binary file not shown.
17 changes: 17 additions & 0 deletions gcloud/compute/demo/demo.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Welcome to the gCloud Compute Demo! (hit enter)

# We're going to walk through some of the basics...,
# Don't worry though. You don't need to do anything, just keep hitting enter...

# Let's start by importing the demo module and getting a connection:
from gcloud.compute import demo
connection = demo.get_connection()

# OK, now let's retrieve an instance
instance = connection.get_instance('gcloud-computeengine-instance',
'us-central1-b')

# Let us give that instance a reset - Got the reset!
instance.reset()

# Thats it for now more is coming soon
18 changes: 18 additions & 0 deletions gcloud/compute/exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# TODO: Make these super useful.


class ComputeError(Exception):
pass


class ConnectionError(ComputeError):

def __init__(self, response, content):
message = str(response) + content
super(ConnectionError, self).__init__(message)


class NotFoundError(ConnectionError):

def __init__(self, response, content):
self.message = 'GET %s returned a 404.' % (response.url)
33 changes: 33 additions & 0 deletions gcloud/compute/instance.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
class Instance(object):

def __init__(self, connection=None, name=None, zone=None):
self.connection = connection
self.name = name
self.zone = zone

@classmethod
def from_dict(cls, instance_dict, connection=None):
"""Construct a new bucket from a dictionary of data from Cloud Storage.

:type bucket_dict: dict
:param bucket_dict: The dictionary of data to construct a bucket from.

:rtype: :class:`Bucket`
:returns: A bucket constructed from the data provided.
"""

return cls(connection=connection, name=instance_dict['name'],
zone=instance_dict['zone'].split('/').pop())

@property
def path(self):
"""The URL path to this instances."""

if not self.name:
raise ValueError('Cannot determine path without instance zone and name.')

return ('projects/%s/zones/%s/instances/%s/' %
(self.connection.project_name, self.zone, self.name))

def reset(self):
return self.connection.reset_instance(self)