diff --git a/django-cloudlaunch/baselaunch/admin.py b/django-cloudlaunch/baselaunch/admin.py index 543d8ade..1a3b5e95 100644 --- a/django-cloudlaunch/baselaunch/admin.py +++ b/django-cloudlaunch/baselaunch/admin.py @@ -59,6 +59,11 @@ class OSCredsInline(admin.StackedInline): formset = forms.DefaultRequiredInlineFormSet extra = 1 +class GCECredsInline(admin.StackedInline): + model = models.GCECredentials + form = forms.GCECredentialsForm + formset = forms.DefaultRequiredInlineFormSet + extra = 1 class AzureCredsInline(admin.StackedInline): model = models.AzureCredentials @@ -68,7 +73,7 @@ class AzureCredsInline(admin.StackedInline): class UserProfileAdmin(admin.ModelAdmin): - inlines = [AWSCredsInline, OSCredsInline, AzureCredsInline] + inlines = [AWSCredsInline, OSCredsInline, AzureCredsInline, GCECredsInline] class AppDeploymentsAdmin(admin.ModelAdmin): @@ -118,6 +123,7 @@ class PublicServicesAdmin(admin.ModelAdmin): admin.site.register(models.S3, S3Admin) admin.site.register(models.Azure, CloudAdmin) admin.site.register(models.OpenStack, CloudAdmin) +admin.site.register(models.GCE, CloudAdmin) admin.site.register(models.UserProfile, UserProfileAdmin) admin.site.register(models.Usage, UsageAdmin) diff --git a/django-cloudlaunch/baselaunch/backend_plugins/cloudman_app.py b/django-cloudlaunch/baselaunch/backend_plugins/cloudman_app.py index e5837334..48181f66 100644 --- a/django-cloudlaunch/baselaunch/backend_plugins/cloudman_app.py +++ b/django-cloudlaunch/baselaunch/backend_plugins/cloudman_app.py @@ -101,17 +101,6 @@ def process_app_config(name, cloud_version_config, credentials, app_config): ec2_creds = provider.security.get_or_create_ec2_credentials() user_data['access_key'] = ec2_creds.access user_data['secret_key'] = ec2_creds.secret - - if hasattr(cloud, 'azure'): - user_data['cloud_type'] = 'azure' - user_data['region_name'] = cloud.azure.region_name - user_data['resource_group'] = cloud.azure.resource_group - user_data['storage_account'] = cloud.azure.storage_account - user_data['vm_default_user_name'] = cloud.azure.vm_default_user_name - user_data['subscription_id'] = credentials.get('azure_subscription_id') - user_data['client_id'] = credentials.get('azure_client_id') - user_data['secret'] = credentials.get('azure_secret') - user_data['tenant'] = credentials.get('azure_tenant') else: raise ValidationError({ "error": "This version of CloudMan supports only EC2-compatible clouds."}) diff --git a/django-cloudlaunch/baselaunch/domain_model.py b/django-cloudlaunch/baselaunch/domain_model.py index c1587ca8..9e065d83 100644 --- a/django-cloudlaunch/baselaunch/domain_model.py +++ b/django-cloudlaunch/baselaunch/domain_model.py @@ -55,5 +55,13 @@ def get_cloud_provider(cloud, cred_dict): config.update(cred_dict) return CloudProviderFactory().create_provider(ProviderList.AZURE, config) + elif isinstance(cloud, models.GCE): + config = { + 'gce_service_creds_dict': cred_dict, + 'gce_default_zone': cloud.zone_name, + 'gce_region_name': cloud.region_name + } + return CloudProviderFactory().create_provider(ProviderList.GCE, + config) else: raise Exception("Unrecognised cloud provider: %s" % cloud) diff --git a/django-cloudlaunch/baselaunch/forms.py b/django-cloudlaunch/baselaunch/forms.py index 1dcee225..72241a83 100644 --- a/django-cloudlaunch/baselaunch/forms.py +++ b/django-cloudlaunch/baselaunch/forms.py @@ -36,6 +36,16 @@ class Meta: model = models.OpenStackCredentials fields = '__all__' +class GCECredentialsForm(ModelForm): + + def __init__(self, *args, **kwargs): + super(GCECredentialsForm, self).__init__(*args, **kwargs) + # restrict choices to GCE clouds only + self.fields['cloud'].queryset = models.GCE.objects.all() + + class Meta: + model = models.GCECredentials + fields = '__all__' class AzureCredentialsForm(ModelForm): diff --git a/django-cloudlaunch/baselaunch/migrations/0019_gce_gcecredentials.py b/django-cloudlaunch/baselaunch/migrations/0019_gce_gcecredentials.py new file mode 100644 index 00000000..3e546577 --- /dev/null +++ b/django-cloudlaunch/baselaunch/migrations/0019_gce_gcecredentials.py @@ -0,0 +1,42 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9 on 2017-06-21 14:35 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion +import fernet_fields.fields + + +class Migration(migrations.Migration): + + dependencies = [ + ('baselaunch', '0018_auto_20170613_1553'), + ] + + operations = [ + migrations.CreateModel( + name='GCE', + fields=[ + ('cloud_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='baselaunch.Cloud')), + ('region_name', models.CharField(max_length=100)), + ('zone_name', models.CharField(max_length=100)), + ], + options={ + 'verbose_name': 'GCE', + 'verbose_name_plural': 'GCE', + }, + bases=('baselaunch.cloud',), + ), + migrations.CreateModel( + name='GCECredentials', + fields=[ + ('credentials_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='baselaunch.Credentials')), + ('credentials', fernet_fields.fields.EncryptedTextField()), + ], + options={ + 'verbose_name': 'GCE Credentials', + 'verbose_name_plural': 'GCE Credentials', + }, + bases=('baselaunch.credentials',), + ), + ] diff --git a/django-cloudlaunch/baselaunch/migrations/0021_merge.py b/django-cloudlaunch/baselaunch/migrations/0021_merge.py new file mode 100644 index 00000000..5e69be8c --- /dev/null +++ b/django-cloudlaunch/baselaunch/migrations/0021_merge.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9 on 2017-07-27 00:54 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('baselaunch', '0020_azure_model_change'), + ('baselaunch', '0019_gce_gcecredentials'), + ] + + operations = [ + ] diff --git a/django-cloudlaunch/baselaunch/models.py b/django-cloudlaunch/baselaunch/models.py index c63bceb0..9e98839c 100644 --- a/django-cloudlaunch/baselaunch/models.py +++ b/django-cloudlaunch/baselaunch/models.py @@ -2,6 +2,7 @@ from django.db import models from django.template.defaultfilters import slugify from fernet_fields import EncryptedCharField +from fernet_fields import EncryptedTextField from model_utils.managers import InheritanceManager from smart_selects.db_fields import ChainedForeignKey @@ -101,6 +102,13 @@ class Meta: verbose_name = "OpenStack" verbose_name_plural = "OpenStack" +class GCE(Cloud): + region_name = models.CharField(max_length=100, blank=False, null=False) + zone_name = models.CharField(max_length=100, blank=False, null=False) + + class Meta: + verbose_name = "GCE" + verbose_name_plural = "GCE" class Azure(Cloud): resource_group = models.CharField(max_length=100, blank=True, null=False) @@ -313,6 +321,24 @@ def as_dict(self): d['os_user_domain_name'] = self.user_domain_name return d +class GCECredentials(Credentials): + credentials = EncryptedTextField(blank=False, null=False) + + def save(self, *args, **kwargs): + if self.credentials: + try: + json.loads(self.credentials) + except Exception as e: + raise Exception("Invalid JSON syntax. GCE Credentials must be in JSON format. Cause: {0}".format(e)) + + super(GCECredentials, self).save(*args, **kwargs) + + class Meta: + verbose_name = "GCE Credentials" + verbose_name_plural = "GCE Credentials" + + def as_dict(self): + return json.loads(self.credentials) class AzureCredentials(Credentials): subscription_id = models.CharField(max_length=50, blank=False, null=False) diff --git a/django-cloudlaunch/baselaunch/serializers.py b/django-cloudlaunch/baselaunch/serializers.py index 4dc330de..279a6750 100644 --- a/django-cloudlaunch/baselaunch/serializers.py +++ b/django-cloudlaunch/baselaunch/serializers.py @@ -511,6 +511,8 @@ def get_region_name(self, obj): return obj.openstack.region_name elif hasattr(obj, 'azure'): return obj.azure.region_name + elif hasattr(obj, 'gce'): + return obj.gce.region_name else: return "Cloud provider not recognized" @@ -521,6 +523,8 @@ def get_cloud_type(self, obj): return 'openstack'; elif hasattr(obj, 'azure'): return 'azure'; + elif hasattr(obj, 'gce'): + return 'gce'; else: return 'unknown'; @@ -557,6 +561,12 @@ def get_extra_data(self, obj): 'storage_account': azure.storage_account, 'vm_default_user_name': azure.vm_default_user_name } + elif hasattr(obj, 'gce'): + gce = obj.gce + return { + 'region_name': gce.region_name, + 'zone_name': gce.zone_name + } else: return {} @@ -754,6 +764,8 @@ class CredentialsSerializer(serializers.Serializer): view_name='openstackcredentials-list') azure = CustomHyperlinkedIdentityField( view_name='azurecredentials-list') + gce = CustomHyperlinkedIdentityField( + view_name='gcecredentials-list') class AWSCredsSerializer(serializers.HyperlinkedModelSerializer): @@ -801,10 +813,25 @@ class Meta: exclude = ('secret', 'user_profile') +class GCECredsSerializer(serializers.HyperlinkedModelSerializer): + id = serializers.IntegerField(read_only=True) + credentials = serializers.CharField( + write_only=True, + style={'base_template': 'textarea.html', 'rows': 20}, + ) + cloud_id = serializers.CharField(write_only=True) + cloud = CloudSerializer(read_only=True) + + class Meta: + model = models.GCECredentials + exclude = ('user_profile',) + + class CloudConnectionAuthSerializer(serializers.Serializer): aws_creds = AWSCredsSerializer(write_only=True, required=False) openstack_creds = OpenstackCredsSerializer(write_only=True, required=False) azure_creds = AzureCredsSerializer(write_only=True, required=False) + gce_creds = GCECredsSerializer(write_only=True, required=False) result = serializers.CharField(read_only=True) details = serializers.CharField(read_only=True) @@ -824,6 +851,7 @@ class UserSerializer(UserDetailsSerializer): aws_creds = serializers.SerializerMethodField() openstack_creds = serializers.SerializerMethodField() azure_creds = serializers.SerializerMethodField() + gce_creds = serializers.SerializerMethodField() def get_aws_creds(self, obj): """ @@ -861,9 +889,21 @@ def get_azure_creds(self, obj): except models.UserProfile.DoesNotExist: return "" + def get_gce_creds(self, obj): + """ + Include a URL for listing this bucket's contents + """ + try: + creds = obj.userprofile.credentials.filter( + gcecredentials__isnull=False).select_subclasses() + return GCECredsSerializer(instance=creds, many=True, + context=self.context).data + except models.UserProfile.DoesNotExist: + return "" + class Meta(UserDetailsSerializer.Meta): fields = UserDetailsSerializer.Meta.fields + \ - ('aws_creds', 'openstack_creds', 'azure_creds', 'credentials') + ('aws_creds', 'openstack_creds', 'azure_creds', 'gce_creds', 'credentials') ### Public Services Serializers ### diff --git a/django-cloudlaunch/baselaunch/urls.py b/django-cloudlaunch/baselaunch/urls.py index c2a2cfb6..1d6a982d 100644 --- a/django-cloudlaunch/baselaunch/urls.py +++ b/django-cloudlaunch/baselaunch/urls.py @@ -116,6 +116,8 @@ views.OpenstackCredentialsViewSet) profile_router.register(r'credentials/azure', views.AzureCredentialsViewSet) +profile_router.register(r'credentials/gce', + views.GCECredentialsViewSet) infrastructure_regex_pattern = r'api/v1/infrastructure/' auth_regex_pattern = r'api/v1/auth/' diff --git a/django-cloudlaunch/baselaunch/view_helpers.py b/django-cloudlaunch/baselaunch/view_helpers.py index 28453181..65136309 100644 --- a/django-cloudlaunch/baselaunch/view_helpers.py +++ b/django-cloudlaunch/baselaunch/view_helpers.py @@ -1,6 +1,7 @@ from baselaunch import domain_model from baselaunch import models +import json def get_cloud_provider(view, cloud_id = None): """ @@ -86,6 +87,13 @@ def get_credentials_from_request(cloud, request): } else: return {} + elif isinstance(cloud, models.GCE): + gce_credentials_json = request.META.get('HTTP_CL_GCE_CREDENTIALS_JSON') + + if gce_credentials_json: + return json.loads(gce_credentials_json) + else: + return {} else: raise Exception("Unrecognised cloud provider: %s" % cloud) diff --git a/django-cloudlaunch/baselaunch/views.py b/django-cloudlaunch/baselaunch/views.py index c8e69f03..f261e175 100644 --- a/django-cloudlaunch/baselaunch/views.py +++ b/django-cloudlaunch/baselaunch/views.py @@ -494,6 +494,7 @@ def get_queryset(self): openstackcredentials__isnull=False).select_subclasses() return models.OpenStackCredentials.objects.none() + class AzureCredentialsViewSet(CredentialsViewSet): """ API endpoint that allows Azure credentials to be viewed or edited. @@ -510,6 +511,22 @@ def get_queryset(self): return models.AzureCredentials.objects.none() +class GCECredentialsViewSet(CredentialsViewSet): + """ + API endpoint that allows GCE credentials to be viewed or edited. + """ + queryset = models.GCECredentials.objects.all() + serializer_class = serializers.GCECredsSerializer + #permission_classes = [permissions.DjangoModelPermissions] + + def get_queryset(self): + user = self.request.user + if hasattr(user, 'userprofile'): + return user.userprofile.credentials.filter( + gcecredentials__isnull=False).select_subclasses() + return models.GCECredentials.objects.none() + + class DeploymentViewSet(viewsets.ModelViewSet): """ List compute related urls. diff --git a/django-cloudlaunch/cloudlaunch/settings.py b/django-cloudlaunch/cloudlaunch/settings.py index 5c06dcc9..82e125a9 100644 --- a/django-cloudlaunch/cloudlaunch/settings.py +++ b/django-cloudlaunch/cloudlaunch/settings.py @@ -59,8 +59,8 @@ 'cl-azure-secret', 'cl-azure-tenant', 'cl-storage-account', - 'cl-azure-vm-default-user-name' - + 'cl-azure-vm-default-user-name', + 'cl-gce-credentials-json', ) # End: django-cors-headers settings