summaryrefslogtreecommitdiff
path: root/openstack_dashboard/dashboards/project/stacks/forms.py
diff options
context:
space:
mode:
Diffstat (limited to 'openstack_dashboard/dashboards/project/stacks/forms.py')
-rw-r--r--openstack_dashboard/dashboards/project/stacks/forms.py488
1 files changed, 0 insertions, 488 deletions
diff --git a/openstack_dashboard/dashboards/project/stacks/forms.py b/openstack_dashboard/dashboards/project/stacks/forms.py
deleted file mode 100644
index e8b9702..0000000
--- a/openstack_dashboard/dashboards/project/stacks/forms.py
+++ /dev/null
@@ -1,488 +0,0 @@
1# Licensed under the Apache License, Version 2.0 (the "License"); you may
2# not use this file except in compliance with the License. You may obtain
3# a copy of the License at
4#
5# http://www.apache.org/licenses/LICENSE-2.0
6#
7# Unless required by applicable law or agreed to in writing, software
8# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10# License for the specific language governing permissions and limitations
11# under the License.
12
13import json
14import logging
15
16import django
17from django.conf import settings
18from django.utils import html
19from django.utils.translation import ugettext_lazy as _
20from django.views.decorators.debug import sensitive_variables
21
22from oslo_utils import strutils
23import six
24
25from horizon import exceptions
26from horizon import forms
27from horizon import messages
28
29from openstack_dashboard import api
30from openstack_dashboard.dashboards.project.images \
31 import utils as image_utils
32from openstack_dashboard.dashboards.project.instances \
33 import utils as instance_utils
34
35
36LOG = logging.getLogger(__name__)
37
38
39def create_upload_form_attributes(prefix, input_type, name):
40 """Creates attribute dicts for the switchable upload form
41
42 :type prefix: str
43 :param prefix: prefix (environment, template) of field
44 :type input_type: str
45 :param input_type: field type (file, raw, url)
46 :type name: str
47 :param name: translated text label to display to user
48 :rtype: dict
49 :return: an attribute set to pass to form build
50 """
51 attributes = {'class': 'switched', 'data-switch-on': prefix + 'source'}
52 attributes['data-' + prefix + 'source-' + input_type] = name
53 return attributes
54
55
56class TemplateForm(forms.SelfHandlingForm):
57
58 class Meta(object):
59 name = _('Select Template')
60 help_text = _('Select a template to launch a stack.')
61
62 # TODO(jomara) - update URL choice for template & environment files
63 # w/ client side download when applicable
64 base_choices = [('file', _('File')),
65 ('raw', _('Direct Input'))]
66 url_choice = [('url', _('URL'))]
67 attributes = {'class': 'switchable', 'data-slug': 'templatesource'}
68 template_source = forms.ChoiceField(label=_('Template Source'),
69 choices=base_choices + url_choice,
70 widget=forms.ThemableSelectWidget(
71 attrs=attributes))
72
73 attributes = create_upload_form_attributes(
74 'template',
75 'file',
76 _('Template File'))
77 template_upload = forms.FileField(
78 label=_('Template File'),
79 help_text=_('A local template to upload.'),
80 widget=forms.FileInput(attrs=attributes),
81 required=False)
82
83 attributes = create_upload_form_attributes(
84 'template',
85 'url',
86 _('Template URL'))
87 template_url = forms.URLField(
88 label=_('Template URL'),
89 help_text=_('An external (HTTP) URL to load the template from.'),
90 widget=forms.TextInput(attrs=attributes),
91 required=False)
92
93 attributes = create_upload_form_attributes(
94 'template',
95 'raw',
96 _('Template Data'))
97 template_data = forms.CharField(
98 label=_('Template Data'),
99 help_text=_('The raw contents of the template.'),
100 widget=forms.widgets.Textarea(attrs=attributes),
101 required=False)
102
103 attributes = {'data-slug': 'envsource', 'class': 'switchable'}
104 environment_source = forms.ChoiceField(
105 label=_('Environment Source'),
106 choices=base_choices,
107 widget=forms.ThemableSelectWidget(attrs=attributes),
108 required=False)
109
110 attributes = create_upload_form_attributes(
111 'env',
112 'file',
113 _('Environment File'))
114 environment_upload = forms.FileField(
115 label=_('Environment File'),
116 help_text=_('A local environment to upload.'),
117 widget=forms.FileInput(attrs=attributes),
118 required=False)
119
120 attributes = create_upload_form_attributes(
121 'env',
122 'raw',
123 _('Environment Data'))
124 environment_data = forms.CharField(
125 label=_('Environment Data'),
126 help_text=_('The raw contents of the environment file.'),
127 widget=forms.widgets.Textarea(attrs=attributes),
128 required=False)
129
130 if django.VERSION >= (1, 9):
131 # Note(Itxaka): On django>=1.9 Charfield has an strip option that
132 # we need to set to False as to not hit
133 # https://bugs.launchpad.net/python-heatclient/+bug/1546166
134 environment_data.strip = False
135 template_data.strip = False
136
137 def __init__(self, *args, **kwargs):
138 self.next_view = kwargs.pop('next_view')
139 super(TemplateForm, self).__init__(*args, **kwargs)
140
141 def clean(self):
142 cleaned = super(TemplateForm, self).clean()
143
144 files = self.request.FILES
145 self.clean_uploaded_files('template', _('template'), cleaned, files)
146 self.clean_uploaded_files('environment', _('environment'), cleaned,
147 files)
148
149 # Validate the template and get back the params.
150 kwargs = {}
151 if cleaned['environment_data']:
152 kwargs['environment'] = cleaned['environment_data']
153 try:
154 files, tpl =\
155 api.heat.get_template_files(cleaned.get('template_data'),
156 cleaned.get('template_url'))
157 kwargs['files'] = files
158 kwargs['template'] = tpl
159 validated = api.heat.template_validate(self.request, **kwargs)
160 cleaned['template_validate'] = validated
161 cleaned['template_validate']['files'] = files
162 cleaned['template_validate']['template'] = tpl
163 except Exception as e:
164 raise forms.ValidationError(six.text_type(e))
165
166 return cleaned
167
168 def clean_uploaded_files(self, prefix, field_label, cleaned, files):
169 """Cleans Template & Environment data from form upload.
170
171 Does some of the crunchy bits for processing uploads vs raw
172 data depending on what the user specified. Identical process
173 for environment data & template data.
174
175 :type prefix: str
176 :param prefix: prefix (environment, template) of field
177 :type field_label: str
178 :param field_label: translated prefix str for messages
179 :type input_type: dict
180 :param prefix: existing cleaned fields from form
181 :rtype: dict
182 :return: cleaned dict including environment & template data
183 """
184
185 upload_str = prefix + "_upload"
186 data_str = prefix + "_data"
187 url = cleaned.get(prefix + '_url')
188 data = cleaned.get(prefix + '_data')
189
190 has_upload = upload_str in files
191 # Uploaded file handler
192 if has_upload and not url:
193 log_template_name = files[upload_str].name
194 LOG.info('got upload %s', log_template_name)
195
196 tpl = files[upload_str].read()
197 if tpl.startswith('{'):
198 try:
199 json.loads(tpl)
200 except Exception as e:
201 msg = _('There was a problem parsing the'
202 ' %(prefix)s: %(error)s')
203 msg = msg % {'prefix': prefix, 'error': six.text_type(e)}
204 raise forms.ValidationError(msg)
205 cleaned[data_str] = tpl
206
207 # URL handler
208 elif url and (has_upload or data):
209 msg = _('Please specify a %s using only one source method.')
210 msg = msg % field_label
211 raise forms.ValidationError(msg)
212
213 elif prefix == 'template':
214 # Check for raw template input - blank environment allowed
215 if not url and not data:
216 msg = _('You must specify a template via one of the '
217 'available sources.')
218 raise forms.ValidationError(msg)
219
220 def create_kwargs(self, data):
221 kwargs = {'parameters': data['template_validate'],
222 'environment_data': data['environment_data']}
223 if data.get('stack_id'):
224 kwargs['stack_id'] = data['stack_id']
225 return kwargs
226
227 def handle(self, request, data):
228 kwargs = self.create_kwargs(data)
229 # NOTE (gabriel): This is a bit of a hack, essentially rewriting this
230 # request so that we can chain it as an input to the next view...
231 # but hey, it totally works.
232 request.method = 'GET'
233
234 return self.next_view.as_view()(request, **kwargs)
235
236
237class ChangeTemplateForm(TemplateForm):
238 class Meta(object):
239 name = _('Edit Template')
240 help_text = _('Select a new template to re-launch a stack.')
241 stack_id = forms.CharField(label=_('Stack ID'),
242 widget=forms.widgets.HiddenInput)
243 stack_name = forms.CharField(label=_('Stack Name'),
244 widget=forms.TextInput(attrs={'readonly':
245 'readonly'}))
246
247
248class PreviewTemplateForm(TemplateForm):
249 class Meta(object):
250 name = _('Preview Template')
251 help_text = _('Select a new template to preview a stack.')
252
253
254class CreateStackForm(forms.SelfHandlingForm):
255
256 param_prefix = '__param_'
257
258 class Meta(object):
259 name = _('Create Stack')
260
261 environment_data = forms.CharField(
262 widget=forms.widgets.HiddenInput,
263 required=False)
264 if django.VERSION >= (1, 9):
265 # Note(Itxaka): On django>=1.9 Charfield has an strip option that
266 # we need to set to False as to not hit
267 # https://bugs.launchpad.net/python-heatclient/+bug/1546166
268 environment_data.strip = False
269
270 parameters = forms.CharField(
271 widget=forms.widgets.HiddenInput)
272 stack_name = forms.RegexField(
273 max_length=255,
274 label=_('Stack Name'),
275 help_text=_('Name of the stack to create.'),
276 regex=r"^[a-zA-Z][a-zA-Z0-9_.-]*$",
277 error_messages={'invalid':
278 _('Name must start with a letter and may '
279 'only contain letters, numbers, underscores, '
280 'periods and hyphens.')})
281 timeout_mins = forms.IntegerField(
282 initial=60,
283 label=_('Creation Timeout (minutes)'),
284 help_text=_('Stack creation timeout in minutes.'))
285 enable_rollback = forms.BooleanField(
286 label=_('Rollback On Failure'),
287 help_text=_('Enable rollback on create/update failure.'),
288 required=False)
289
290 def __init__(self, *args, **kwargs):
291 parameters = kwargs.pop('parameters')
292 # special case: load template data from API, not passed in params
293 if kwargs.get('validate_me'):
294 parameters = kwargs.pop('validate_me')
295 super(CreateStackForm, self).__init__(*args, **kwargs)
296
297 if self._stack_password_enabled():
298 self.fields['password'] = forms.CharField(
299 label=_('Password for user "%s"') % self.request.user.username,
300 help_text=_('This is required for operations to be performed '
301 'throughout the lifecycle of the stack'),
302 widget=forms.PasswordInput())
303
304 self._build_parameter_fields(parameters)
305
306 def _stack_password_enabled(self):
307 stack_settings = getattr(settings, 'OPENSTACK_HEAT_STACK', {})
308 return stack_settings.get('enable_user_pass', True)
309
310 def _build_parameter_fields(self, template_validate):
311 self.help_text = template_validate['Description']
312
313 params = template_validate.get('Parameters', {})
314 if template_validate.get('ParameterGroups'):
315 params_in_order = []
316 for group in template_validate['ParameterGroups']:
317 for param in group.get('parameters', []):
318 if param in params:
319 params_in_order.append((param, params[param]))
320 else:
321 # no parameter groups, simply sorted to make the order fixed
322 params_in_order = sorted(params.items())
323 for param_key, param in params_in_order:
324 field = None
325 field_key = self.param_prefix + param_key
326 initial = param.get('Value',
327 param.get('DefaultValue',
328 param.get('Default')))
329 field_args = {
330 'initial': initial,
331 'label': param.get('Label', param_key),
332 'help_text': html.escape(param.get('Description', '')),
333 'required': initial is None,
334 }
335
336 param_type = param.get('Type', None)
337 hidden = strutils.bool_from_string(param.get('NoEcho', 'false'))
338 if 'CustomConstraint' in param:
339 choices = self._populate_custom_choices(
340 param['CustomConstraint'])
341 field_args['choices'] = choices
342 field = forms.ChoiceField(**field_args)
343
344 elif 'AllowedValues' in param:
345 choices = map(lambda x: (x, x), param['AllowedValues'])
346 field_args['choices'] = choices
347 field = forms.ChoiceField(**field_args)
348
349 elif param_type == 'Json' and 'Default' in param:
350 field_args['initial'] = json.dumps(param['Default'])
351 field = forms.CharField(**field_args)
352
353 elif param_type in ('CommaDelimitedList', 'String', 'Json'):
354 if 'MinLength' in param:
355 field_args['min_length'] = int(param['MinLength'])
356 field_args['required'] = field_args['min_length'] > 0
357 if 'MaxLength' in param:
358 field_args['max_length'] = int(param['MaxLength'])
359 if hidden:
360 field_args['widget'] = forms.PasswordInput(
361 render_value=True)
362 field = forms.CharField(**field_args)
363
364 elif param_type == 'Number':
365 if 'MinValue' in param:
366 field_args['min_value'] = int(param['MinValue'])
367 if 'MaxValue' in param:
368 field_args['max_value'] = int(param['MaxValue'])
369 field = forms.IntegerField(**field_args)
370
371 elif param_type == 'Boolean':
372 field_args['required'] = False
373 field = forms.BooleanField(**field_args)
374
375 if field:
376 self.fields[field_key] = field
377
378 @sensitive_variables('password')
379 def handle(self, request, data):
380 prefix_length = len(self.param_prefix)
381 params_list = [(k[prefix_length:], v) for (k, v) in data.items()
382 if k.startswith(self.param_prefix)]
383 fields = {
384 'stack_name': data.get('stack_name'),
385 'timeout_mins': data.get('timeout_mins'),
386 'disable_rollback': not(data.get('enable_rollback')),
387 'parameters': dict(params_list),
388 'files': json.loads(data.get('parameters')).get('files'),
389 'template': json.loads(data.get('parameters')).get('template')
390 }
391 if data.get('password'):
392 fields['password'] = data.get('password')
393
394 if data.get('environment_data'):
395 fields['environment'] = data.get('environment_data')
396
397 try:
398 api.heat.stack_create(self.request, **fields)
399 messages.info(request, _("Stack creation started."))
400 return True
401 except Exception:
402 exceptions.handle(request)
403
404 def _populate_custom_choices(self, custom_type):
405 if custom_type == 'neutron.network':
406 return instance_utils.network_field_data(self.request, True)
407 if custom_type == 'nova.keypair':
408 return instance_utils.keypair_field_data(self.request, True)
409 if custom_type == 'glance.image':
410 return image_utils.image_field_data(self.request, True)
411 if custom_type == 'nova.flavor':
412 return instance_utils.flavor_field_data(self.request, True)
413 return []
414
415
416class EditStackForm(CreateStackForm):
417
418 class Meta(object):
419 name = _('Update Stack Parameters')
420
421 stack_id = forms.CharField(
422 label=_('Stack ID'),
423 widget=forms.widgets.HiddenInput)
424 stack_name = forms.CharField(
425 label=_('Stack Name'),
426 widget=forms.TextInput(attrs={'readonly': 'readonly'}))
427
428 @sensitive_variables('password')
429 def handle(self, request, data):
430 prefix_length = len(self.param_prefix)
431 params_list = [(k[prefix_length:], v) for (k, v) in data.items()
432 if k.startswith(self.param_prefix)]
433
434 stack_id = data.get('stack_id')
435 fields = {
436 'stack_name': data.get('stack_name'),
437 'timeout_mins': data.get('timeout_mins'),
438 'disable_rollback': not(data.get('enable_rollback')),
439 'parameters': dict(params_list),
440 'files': json.loads(data.get('parameters')).get('files'),
441 'template': json.loads(data.get('parameters')).get('template')
442 }
443 if data.get('password'):
444 fields['password'] = data.get('password')
445
446 if data.get('environment_data'):
447 fields['environment'] = data.get('environment_data')
448
449 try:
450 api.heat.stack_update(self.request, stack_id=stack_id, **fields)
451 messages.info(request, _("Stack update started."))
452 return True
453 except Exception:
454 exceptions.handle(request)
455
456
457class PreviewStackForm(CreateStackForm):
458
459 class Meta(object):
460 name = _('Preview Stack Parameters')
461
462 def __init__(self, *args, **kwargs):
463 self.next_view = kwargs.pop('next_view')
464 super(CreateStackForm, self).__init__(*args, **kwargs)
465
466 def handle(self, request, data):
467 prefix_length = len(self.param_prefix)
468 params_list = [(k[prefix_length:], v) for (k, v) in data.items()
469 if k.startswith(self.param_prefix)]
470 fields = {
471 'stack_name': data.get('stack_name'),
472 'timeout_mins': data.get('timeout_mins'),
473 'disable_rollback': not(data.get('enable_rollback')),
474 'parameters': dict(params_list),
475 'files': json.loads(data.get('parameters')).get('files'),
476 'template': json.loads(data.get('parameters')).get('template')
477 }
478
479 if data.get('environment_data'):
480 fields['environment'] = data.get('environment_data')
481
482 try:
483 stack_preview = api.heat.stack_preview(self.request, **fields)
484 request.method = 'GET'
485 return self.next_view.as_view()(request,
486 stack_preview=stack_preview)
487 except Exception:
488 exceptions.handle(request)