Beginning PEM file support for GCE.

This commit is contained in:
Luke Sneeringer 2014-07-18 15:54:23 -05:00
parent ca19ddb3d5
commit dd79ba8de7
6 changed files with 95 additions and 4 deletions

View File

@ -966,6 +966,7 @@ class InventorySourceOptionsSerializer(BaseSerializer):
field_opts = metadata.get('source_regions', {})
field_opts['ec2_region_choices'] = self.opts.model.get_ec2_region_choices()
field_opts['rax_region_choices'] = self.opts.model.get_rax_region_choices()
field_opts['gce_region_choices'] = self.opts.model.get_gce_region_choices()
return metadata
def to_native(self, obj):

View File

@ -4,6 +4,8 @@
# Django
from django.db import models
from django.db.models.fields.related import SingleRelatedObjectDescriptor
from django.utils.translation import ugettext_lazy as _
# South
from south.modelsinspector import add_introspection_rules
@ -38,3 +40,57 @@ class AutoOneToOneField(models.OneToOneField):
add_introspection_rules([([AutoOneToOneField], [], {})],
[r'^awx\.main\.fields\.AutoOneToOneField'])
# Copied, flat out, from Django 1.6.
# Vendored here because Tower is run against Django 1.5.
#
# Original:
# github.com/django/django/blob/master/django/db/models/fields/__init__.py
#
# Django is:
# Copyright (c) Django Software Foundation and individual contributors.
# All rights reserved.
#
# Used under license:
# github.com/django/django/blob/master/LICENSE
class BinaryField(models.Field):
description = _("Raw binary data")
empty_values = [None, b'']
def __init__(self, *args, **kwargs):
kwargs['editable'] = False
super(BinaryField, self).__init__(*args, **kwargs)
if self.max_length is not None:
self.validators.append(validators.MaxLengthValidator(self.max_length))
def get_internal_type(self):
return "BinaryField"
def get_default(self):
if self.has_default() and not callable(self.default):
return self.default
default = super(BinaryField, self).get_default()
if default == '':
return b''
return default
def get_db_prep_value(self, value, connection, prepared=False):
value = super(BinaryField, self).get_db_prep_value(value, connection, prepared)
if value is not None:
return connection.Database.Binary(value)
return value
def value_to_string(self, obj):
"""Binary data is serialized as base64"""
return b64encode(force_bytes(self._get_val_from_obj(obj))).decode('ascii')
def to_python(self, value):
# If it's a string, it should be base64-encoded data
if isinstance(value, six.text_type):
return six.memoryview(b64decode(force_bytes(value)))
return value
add_introspection_rules([([BinaryField], [], {})],
[r'^awx\.main\.fields\.BinaryField'])

View File

@ -13,12 +13,27 @@ from django.core.exceptions import ValidationError, NON_FIELD_ERRORS
from django.core.urlresolvers import reverse
# AWX
from awx.main import storage
from awx.main.fields import BinaryField
from awx.main.utils import decrypt_field
from awx.main.models.base import *
__all__ = ['Credential']
class PEM(models.Model):
"""Model representing a PEM p12 private key created with openssl.
These are notably used as credentials for authenticating to Google
services, and Tower uses them for Google Compute Engine.
"""
filename = models.CharField(max_length=100, unique=True)
contents = BinaryField()
class Meta:
app_label = 'main'
class Credential(PasswordFieldsModel, CommonModelNameNotUnique):
'''
A credential contains information about how to talk to a remote resource
@ -36,7 +51,7 @@ class Credential(PasswordFieldsModel, CommonModelNameNotUnique):
]
PASSWORD_FIELDS = ('password', 'ssh_key_data', 'ssh_key_unlock',
'sudo_password', 'vault_password')
'sudo_password', 'vault_password', 'pem_file')
class Meta:
app_label = 'main'
@ -122,6 +137,12 @@ class Credential(PasswordFieldsModel, CommonModelNameNotUnique):
default='',
help_text=_('Vault password (or "ASK" to prompt the user).'),
)
pem_file = models.FileField(
blank=True,
null=True,
upload_to='irrelevant',
storage=storage.DatabaseStorage(PEM),
)
@property
def needs_password(self):
@ -304,7 +325,7 @@ class Credential(PasswordFieldsModel, CommonModelNameNotUnique):
# If update_fields has been specified, add our field names to it,
# if hit hasn't been specified, then we're just doing a normal save.
update_fields = kwargs.get('update_fields', [])
cloud = self.kind in ('aws', 'rax')
cloud = self.kind in ('aws', 'rax', 'gce', 'vmware', 'azure')
if self.cloud != cloud:
self.cloud = cloud
if 'cloud' not in update_fields:

View File

@ -810,6 +810,17 @@ class InventorySourceOptions(BaseModel):
regions.insert(0, ('ALL', 'All'))
return regions
@classmethod
def get_gce_region_choices(self):
"""Return a complete list of regions in GCE, as a list of
two-tuples.
"""
# It's not possible to get a list of regions from GCE without
# authenticating first. Therefore, use a list from settings.
regions = list(getattr(settings, 'GCE_REGION_CHOICES', []))
regions.insert(0, ('all', 'All'))
return regions
def clean_credential(self):
if not self.source:
return None

View File

@ -418,7 +418,7 @@ GCE_REGION_CHOICES = [
('us-central1-b', 'US Central (B)'),
('us-central1-f', 'US Central (F)'),
('europe-west1-a', 'Europe West (A)'),
('europe-west1-b', 'Europe West (B)')
('europe-west1-b', 'Europe West (B)'),
('asia-east1-a', 'Asia East (A)'),
('asia-east1-b', 'Asia East (B)'),
]
@ -427,7 +427,7 @@ GCE_REGIONS_BLACKLIST = []
# Inventory variable name/value for determining whether a host is active
# in Google Compute Engine.
GCE_ENABLED_VAR = 'status'
GCE_ENABLED_VALUE = '<???>'
GCE_ENABLED_VALUE = 'running'
# Filter for allowed group and host names when importing inventory from
# Google Compute Engine.

View File

@ -101,6 +101,8 @@ angular.module('CredentialFormDefinition', [])
'<dd>Access keys for Amazon Web Services used for inventory management or deployment.</dd>\n' +
'<dt>Rackspace</dt>\n' +
'<dd>Access information for Rackspace Cloud used for inventory management or deployment.</dd>\n' +
'<dt>Google Compute Engine</dt>\n' +
'<dd>Credentials for Google Compute Engine, used for inventory management or deployment.</dd>\n' +
'<dt>VMWare</dt>\n' +
'<dd>Access information for VMWare vSphere used for inventory management or deployment.</dd>\n' +
'</dl>\n'