Additional optional Environment data to heat template selection
Change-Id: I4e0d32e7e032d5c0ec93595c14134e780fa21d66 Implements: blueprint heat-environment-file
This commit is contained in:
parent
eabc890792
commit
e59043d3bf
|
@ -53,6 +53,23 @@ def exception_to_validation_msg(e):
|
|||
return match.group(1)
|
||||
|
||||
|
||||
def create_upload_form_attributes(prefix, input_type, name):
|
||||
"""Creates attribute dicts for the switchable upload form
|
||||
|
||||
:type prefix: str
|
||||
:param prefix: prefix (environment, template) of field
|
||||
:type input_type: str
|
||||
:param input_type: field type (file, raw, url)
|
||||
:type name: str
|
||||
:param name: translated text label to display to user
|
||||
:rtype: dict
|
||||
:return: an attribute set to pass to form build
|
||||
"""
|
||||
attributes = {'class': 'switched', 'data-switch-on': prefix + 'source'}
|
||||
attributes['data-' + prefix + 'source-' + input_type] = name
|
||||
return attributes
|
||||
|
||||
|
||||
class TemplateForm(forms.SelfHandlingForm):
|
||||
|
||||
class Meta:
|
||||
|
@ -60,34 +77,79 @@ class TemplateForm(forms.SelfHandlingForm):
|
|||
help_text = _('From here you can select a template to launch '
|
||||
'a stack.')
|
||||
|
||||
choices = [('url', _('URL')),
|
||||
('file', _('File')),
|
||||
('raw', _('Direct Input'))]
|
||||
attributes = {'class': 'switchable', 'data-slug': 'templatesource'}
|
||||
template_source = forms.ChoiceField(label=_('Template Source'),
|
||||
choices=[('url', _('URL')),
|
||||
('file', _('File')),
|
||||
('raw', _('Direct Input'))],
|
||||
widget=forms.Select(attrs={
|
||||
'class': 'switchable',
|
||||
'data-slug': 'source'}))
|
||||
choices=choices,
|
||||
widget=forms.Select(attrs=attributes))
|
||||
|
||||
attributes = create_upload_form_attributes(
|
||||
'template',
|
||||
'file',
|
||||
_('Template File'))
|
||||
template_upload = forms.FileField(
|
||||
label=_('Template File'),
|
||||
help_text=_('A local template to upload.'),
|
||||
widget=forms.FileInput(attrs={'class': 'switched',
|
||||
'data-switch-on': 'source',
|
||||
'data-source-file': _('Template File')}),
|
||||
widget=forms.FileInput(attrs=attributes),
|
||||
required=False)
|
||||
|
||||
attributes = create_upload_form_attributes(
|
||||
'template',
|
||||
'url',
|
||||
_('Template URL'))
|
||||
template_url = forms.URLField(
|
||||
label=_('Template URL'),
|
||||
help_text=_('An external (HTTP) URL to load the template from.'),
|
||||
widget=forms.TextInput(attrs={'class': 'switched',
|
||||
'data-switch-on': 'source',
|
||||
'data-source-url': _('Template URL')}),
|
||||
widget=forms.TextInput(attrs=attributes),
|
||||
required=False)
|
||||
|
||||
attributes = create_upload_form_attributes(
|
||||
'template',
|
||||
'raw',
|
||||
_('Template Data'))
|
||||
template_data = forms.CharField(
|
||||
label=_('Template Data'),
|
||||
help_text=_('The raw contents of the template.'),
|
||||
widget=forms.widgets.Textarea(attrs={
|
||||
'class': 'switched',
|
||||
'data-switch-on': 'source',
|
||||
'data-source-raw': _('Template Data')}),
|
||||
widget=forms.widgets.Textarea(attrs=attributes),
|
||||
required=False)
|
||||
|
||||
attributes = {'data-slug': 'envsource', 'class': 'switchable'}
|
||||
environment_source = forms.ChoiceField(
|
||||
label=_('Environment Source'),
|
||||
choices=choices,
|
||||
widget=forms.Select(attrs=attributes),
|
||||
required=False)
|
||||
|
||||
attributes = create_upload_form_attributes(
|
||||
'env',
|
||||
'file',
|
||||
_('Environment File'))
|
||||
environment_upload = forms.FileField(
|
||||
label=_('Environment File'),
|
||||
help_text=_('A local environment to upload.'),
|
||||
widget=forms.FileInput(attrs=attributes),
|
||||
required=False)
|
||||
|
||||
attributes = create_upload_form_attributes(
|
||||
'env',
|
||||
'url',
|
||||
_('Environment URL'))
|
||||
environment_url = forms.URLField(
|
||||
label=_('Environment URL'),
|
||||
help_text=_('An external (HTTP) URL to load the environment from.'),
|
||||
widget=forms.TextInput(attrs=attributes),
|
||||
required=False)
|
||||
|
||||
attributes = create_upload_form_attributes(
|
||||
'env',
|
||||
'raw',
|
||||
_('Environment Data'))
|
||||
environment_data = forms.CharField(
|
||||
label=_('Environment Data'),
|
||||
help_text=_('The raw contents of the environment file.'),
|
||||
widget=forms.widgets.Textarea(attrs=attributes),
|
||||
required=False)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
|
@ -96,35 +158,13 @@ class TemplateForm(forms.SelfHandlingForm):
|
|||
|
||||
def clean(self):
|
||||
cleaned = super(TemplateForm, self).clean()
|
||||
template_url = cleaned.get('template_url')
|
||||
template_data = cleaned.get('template_data')
|
||||
|
||||
files = self.request.FILES
|
||||
has_upload = 'template_upload' in files
|
||||
|
||||
# Uploaded file handler
|
||||
if has_upload and not template_url:
|
||||
log_template_name = self.request.FILES['template_upload'].name
|
||||
LOG.info('got upload %s' % log_template_name)
|
||||
|
||||
tpl = self.request.FILES['template_upload'].read()
|
||||
if tpl.startswith('{'):
|
||||
try:
|
||||
json.loads(tpl)
|
||||
except Exception as e:
|
||||
msg = _('There was a problem parsing the template: %s') % e
|
||||
raise forms.ValidationError(msg)
|
||||
cleaned['template_data'] = tpl
|
||||
|
||||
# URL handler
|
||||
elif template_url and (has_upload or template_data):
|
||||
msg = _('Please specify a template using only one source method.')
|
||||
raise forms.ValidationError(msg)
|
||||
|
||||
# Check for raw template input
|
||||
elif not template_url and not template_data:
|
||||
msg = _('You must specify a template via one of the '
|
||||
'available sources.')
|
||||
raise forms.ValidationError(msg)
|
||||
self.clean_uploaded_files('template', _('template'), cleaned, files)
|
||||
self.clean_uploaded_files('environment',
|
||||
_('environment'),
|
||||
cleaned,
|
||||
files)
|
||||
|
||||
# Validate the template and get back the params.
|
||||
kwargs = {}
|
||||
|
@ -145,8 +185,62 @@ class TemplateForm(forms.SelfHandlingForm):
|
|||
|
||||
return cleaned
|
||||
|
||||
def clean_uploaded_files(self, prefix, field_label, cleaned, files):
|
||||
"""Cleans Template & Environment data from form upload.
|
||||
|
||||
Does some of the crunchy bits for processing uploads vs raw
|
||||
data depending on what the user specified. Identical process
|
||||
for environment data & template data.
|
||||
|
||||
:type prefix: str
|
||||
:param prefix: prefix (environment, template) of field
|
||||
:type field_label: str
|
||||
:param field_label: translated prefix str for messages
|
||||
:type input_type: dict
|
||||
:param prefix: existing cleaned fields from form
|
||||
:rtype: dict
|
||||
:return: cleaned dict including environment & template data
|
||||
"""
|
||||
|
||||
upload_str = prefix + "_upload"
|
||||
data_str = prefix + "_data"
|
||||
url = cleaned.get(prefix + '_url')
|
||||
data = cleaned.get(prefix + '_data')
|
||||
|
||||
has_upload = upload_str in files
|
||||
# Uploaded file handler
|
||||
if has_upload and not url:
|
||||
log_template_name = files[upload_str].name
|
||||
LOG.info('got upload %s' % log_template_name)
|
||||
|
||||
tpl = files[upload_str].read()
|
||||
if tpl.startswith('{'):
|
||||
try:
|
||||
json.loads(tpl)
|
||||
except Exception as e:
|
||||
msg = _('There was a problem parsing the'
|
||||
' %(prefix)s: %(error)s')
|
||||
msg = msg % {'prefix': prefix, 'error': e}
|
||||
raise forms.ValidationError(msg)
|
||||
cleaned[data_str] = tpl
|
||||
|
||||
# URL handler
|
||||
elif url and (has_upload or data):
|
||||
msg = _('Please specify a %s using only one source method.')
|
||||
msg = msg % field_label
|
||||
raise forms.ValidationError(msg)
|
||||
|
||||
elif prefix == 'template':
|
||||
# Check for raw template input - blank environment allowed
|
||||
if not url and not data:
|
||||
msg = _('You must specify a template via one of the '
|
||||
'available sources.')
|
||||
raise forms.ValidationError(msg)
|
||||
|
||||
def create_kwargs(self, data):
|
||||
kwargs = {'parameters': data['template_validate'],
|
||||
'environment_data': data['environment_data'],
|
||||
'environment_url': data['environment_url'],
|
||||
'template_data': data['template_data'],
|
||||
'template_url': data['template_url']}
|
||||
if data.get('stack_id'):
|
||||
|
@ -190,6 +284,12 @@ class CreateStackForm(forms.SelfHandlingForm):
|
|||
template_url = forms.CharField(
|
||||
widget=forms.widgets.HiddenInput,
|
||||
required=False)
|
||||
environment_data = forms.CharField(
|
||||
widget=forms.widgets.HiddenInput,
|
||||
required=False)
|
||||
environment_url = forms.CharField(
|
||||
widget=forms.widgets.HiddenInput,
|
||||
required=False)
|
||||
parameters = forms.CharField(
|
||||
widget=forms.widgets.HiddenInput,
|
||||
required=True)
|
||||
|
@ -283,6 +383,11 @@ class CreateStackForm(forms.SelfHandlingForm):
|
|||
else:
|
||||
fields['template_url'] = data.get('template_url')
|
||||
|
||||
if data.get('environment_data'):
|
||||
fields['environment'] = data.get('environment_data')
|
||||
elif data.get('environment_url'):
|
||||
fields['environment_url'] = data.get('environment_url')
|
||||
|
||||
try:
|
||||
api.heat.stack_create(self.request, **fields)
|
||||
messages.success(request, _("Stack creation started."))
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
|
||||
import json
|
||||
|
||||
from django.core import exceptions
|
||||
from django.core.urlresolvers import reverse
|
||||
from django import http
|
||||
|
||||
|
@ -155,6 +156,60 @@ class StackTests(test.TestCase):
|
|||
res = self.client.post(url, form_data)
|
||||
self.assertRedirectsNoFollow(res, INDEX_URL)
|
||||
|
||||
@test.create_stubs({api.heat: ('stack_create', 'template_validate')})
|
||||
def test_launch_stackwith_environment(self):
|
||||
template = self.stack_templates.first()
|
||||
environment = self.stack_environments.first()
|
||||
stack = self.stacks.first()
|
||||
|
||||
api.heat.template_validate(IsA(http.HttpRequest),
|
||||
template=template.data) \
|
||||
.AndReturn(json.loads(template.validate))
|
||||
|
||||
api.heat.stack_create(IsA(http.HttpRequest),
|
||||
stack_name=stack.stack_name,
|
||||
timeout_mins=60,
|
||||
disable_rollback=True,
|
||||
template=template.data,
|
||||
environment=environment.data,
|
||||
parameters=IsA(dict),
|
||||
password='password')
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
url = reverse('horizon:project:stacks:select_template')
|
||||
res = self.client.get(url)
|
||||
self.assertTemplateUsed(res, 'project/stacks/select_template.html')
|
||||
|
||||
form_data = {'template_source': 'raw',
|
||||
'template_data': template.data,
|
||||
'environment_source': 'raw',
|
||||
'environment_data': environment.data,
|
||||
'method': forms.TemplateForm.__name__}
|
||||
res = self.client.post(url, form_data)
|
||||
self.assertTemplateUsed(res, 'project/stacks/create.html')
|
||||
|
||||
url = reverse('horizon:project:stacks:launch')
|
||||
form_data = {'template_source': 'raw',
|
||||
'template_data': template.data,
|
||||
'environment_source': 'raw',
|
||||
'environment_data': environment.data,
|
||||
'password': 'password',
|
||||
'parameters': template.validate,
|
||||
'stack_name': stack.stack_name,
|
||||
"timeout_mins": 60,
|
||||
"disable_rollback": True,
|
||||
"__param_DBUsername": "admin",
|
||||
"__param_LinuxDistribution": "F17",
|
||||
"__param_InstanceType": "m1.small",
|
||||
"__param_KeyName": "test",
|
||||
"__param_DBPassword": "admin",
|
||||
"__param_DBRootPassword": "admin",
|
||||
"__param_DBName": "wordpress",
|
||||
'method': forms.CreateStackForm.__name__}
|
||||
res = self.client.post(url, form_data)
|
||||
self.assertRedirectsNoFollow(res, INDEX_URL)
|
||||
|
||||
@test.create_stubs({api.heat: ('stack_update', 'stack_get',
|
||||
'template_get', 'template_validate')})
|
||||
def test_edit_stack_template(self):
|
||||
|
@ -258,6 +313,14 @@ class StackTests(test.TestCase):
|
|||
|
||||
class TemplateFormTests(test.TestCase):
|
||||
|
||||
class SimpleFile(object):
|
||||
def __init__(self, name, data):
|
||||
self.name = name
|
||||
self.data = data
|
||||
|
||||
def read(self):
|
||||
return self.data
|
||||
|
||||
def test_exception_to_validation(self):
|
||||
json_error = """{
|
||||
"code": 400,
|
||||
|
@ -298,3 +361,67 @@ malformed or otherwise incorrect.
|
|||
|
||||
msg = forms.exception_to_validation_msg(json_error)
|
||||
self.assertIsNone(msg)
|
||||
|
||||
def test_create_upload_form_attributes(self):
|
||||
attrs = forms.create_upload_form_attributes(
|
||||
'env', 'url', 'Environment')
|
||||
self.assertEqual(attrs['data-envsource-url'], 'Environment')
|
||||
|
||||
def test_clean_file_upload_form_url(self):
|
||||
kwargs = {'next_view': 'Launch Stack'}
|
||||
t = forms.TemplateForm({}, **kwargs)
|
||||
precleaned = {
|
||||
'template_url': 'http://templateurl.com',
|
||||
}
|
||||
t.clean_uploaded_files('template', 'template', precleaned, {})
|
||||
|
||||
self.assertEqual(precleaned['template_url'], 'http://templateurl.com')
|
||||
|
||||
def test_clean_file_upload_form_multiple(self):
|
||||
kwargs = {'next_view': 'Launch Stack'}
|
||||
t = forms.TemplateForm({}, **kwargs)
|
||||
precleaned = {
|
||||
'template_url': 'http://templateurl.com',
|
||||
'template_data': 'http://templateurl.com',
|
||||
}
|
||||
self.assertRaises(
|
||||
exceptions.ValidationError,
|
||||
t.clean_uploaded_files,
|
||||
'template',
|
||||
'template',
|
||||
precleaned,
|
||||
{})
|
||||
|
||||
def test_clean_file_upload_form_invalid_json(self):
|
||||
kwargs = {'next_view': 'Launch Stack'}
|
||||
t = forms.TemplateForm({}, **kwargs)
|
||||
precleaned = {
|
||||
'template_data': 'http://templateurl.com',
|
||||
}
|
||||
json_str = '{notvalidjson::::::json/////json'
|
||||
files = {'template_upload':
|
||||
self.SimpleFile('template_name', json_str)}
|
||||
|
||||
self.assertRaises(
|
||||
exceptions.ValidationError,
|
||||
t.clean_uploaded_files,
|
||||
'template',
|
||||
'template',
|
||||
precleaned,
|
||||
files)
|
||||
|
||||
def test_clean_file_upload_form_valid_data(self):
|
||||
kwargs = {'next_view': 'Launch Stack'}
|
||||
t = forms.TemplateForm({}, **kwargs)
|
||||
precleaned = {
|
||||
'template_data': 'http://templateurl.com',
|
||||
}
|
||||
|
||||
json_str = '{"isvalid":"json"}'
|
||||
files = {'template_upload':
|
||||
self.SimpleFile('template_name', json_str)}
|
||||
|
||||
t.clean_uploaded_files('template', 'template', precleaned, files)
|
||||
self.assertEqual(
|
||||
json_str,
|
||||
precleaned['template_data'])
|
||||
|
|
|
@ -107,14 +107,19 @@ class CreateStackView(forms.ModalFormView):
|
|||
|
||||
def get_initial(self):
|
||||
initial = {}
|
||||
if 'template_data' in self.kwargs:
|
||||
initial['template_data'] = self.kwargs['template_data']
|
||||
if 'template_url' in self.kwargs:
|
||||
initial['template_url'] = self.kwargs['template_url']
|
||||
self.load_kwargs(initial)
|
||||
if 'parameters' in self.kwargs:
|
||||
initial['parameters'] = json.dumps(self.kwargs['parameters'])
|
||||
return initial
|
||||
|
||||
def load_kwargs(self, initial):
|
||||
# load the "passed through" data from template form
|
||||
for prefix in ('template', 'environment'):
|
||||
for suffix in ('_data', '_url'):
|
||||
key = prefix + suffix
|
||||
if key in self.kwargs:
|
||||
initial[key] = self.kwargs[key]
|
||||
|
||||
def get_form_kwargs(self):
|
||||
kwargs = super(CreateStackView, self).get_form_kwargs()
|
||||
if 'parameters' in self.kwargs:
|
||||
|
|
Loading…
Reference in New Issue