diff --git a/doc/source/topics/settings.rst b/doc/source/topics/settings.rst index d612d2da23..4b28d3e6e8 100755 --- a/doc/source/topics/settings.rst +++ b/doc/source/topics/settings.rst @@ -566,6 +566,7 @@ Default:: ('aki', _('AKI - Amazon Kernel Image')), ('ami', _('AMI - Amazon Machine Image')), ('ari', _('ARI - Amazon Ramdisk Image')), + ('docker', _('Docker')), ('iso', _('ISO - Optical Disk Image')), ('qcow2', _('QCOW2 - QEMU Emulator')), ('raw', _('Raw')), diff --git a/openstack_dashboard/dashboards/admin/images/views.py b/openstack_dashboard/dashboards/admin/images/views.py index 9f1d0c7244..79cc62767c 100644 --- a/openstack_dashboard/dashboards/admin/images/views.py +++ b/openstack_dashboard/dashboards/admin/images/views.py @@ -102,6 +102,10 @@ class IndexView(tables.DataTableView): LOG.warning(invalid_msg) except ValueError: LOG.warning(invalid_msg) + elif (filter_field == 'disk_format' and + filter_string.lower() == 'docker'): + filters['disk_format'] = 'raw' + filters['container_format'] = 'docker' else: filters[filter_field] = filter_string return filters diff --git a/openstack_dashboard/dashboards/project/images/images/forms.py b/openstack_dashboard/dashboards/project/images/images/forms.py index 50c53cc822..4b20764a93 100644 --- a/openstack_dashboard/dashboards/project/images/images/forms.py +++ b/openstack_dashboard/dashboards/project/images/images/forms.py @@ -37,6 +37,49 @@ IMAGE_BACKEND_SETTINGS = getattr(settings, 'OPENSTACK_IMAGE_BACKEND', {}) IMAGE_FORMAT_CHOICES = IMAGE_BACKEND_SETTINGS.get('image_formats', []) +def create_image_metadata(data): + """Use the given dict of image form data to generate the metadata used for + creating the image in glance. + """ + # Glance does not really do anything with container_format at the + # moment. It requires it is set to the same disk_format for the three + # Amazon image types, otherwise it just treats them as 'bare.' As such + # we will just set that to be that here instead of bothering the user + # with asking them for information we can already determine. + disk_format = data['disk_format'] + if disk_format in ('ami', 'aki', 'ari',): + container_format = disk_format + elif disk_format == 'docker': + # To support docker containers we allow the user to specify + # 'docker' as the format. In that case we really want to use + # 'raw' as the disk format and 'docker' as the container format. + disk_format = 'raw' + container_format = 'docker' + else: + container_format = 'bare' + + # The Create form uses 'is_public' but the Update form uses 'public'. Just + # being tolerant here so we don't break anything else. + meta = {'is_public': data.get('is_public', data.get('public', False)), + 'protected': data['protected'], + 'disk_format': disk_format, + 'container_format': container_format, + 'min_disk': (data['minimum_disk'] or 0), + 'min_ram': (data['minimum_ram'] or 0), + 'name': data['name'], + 'properties': {}} + + if data['description']: + meta['properties']['description'] = data['description'] + if data.get('kernel'): + meta['properties']['kernel_id'] = data['kernel'] + if data.get('ramdisk'): + meta['properties']['ramdisk_id'] = data['ramdisk'] + if data.get('architecture'): + meta['properties']['architecture'] = data['architecture'] + return meta + + class CreateImageForm(forms.SelfHandlingForm): name = forms.CharField(max_length=255, label=_("Name")) description = forms.CharField(max_length=255, label=_("Description"), @@ -200,33 +243,9 @@ class CreateImageForm(forms.SelfHandlingForm): return data def handle(self, request, data): - # Glance does not really do anything with container_format at the - # moment. It requires it is set to the same disk_format for the three - # Amazon image types, otherwise it just treats them as 'bare.' As such - # we will just set that to be that here instead of bothering the user - # with asking them for information we can already determine. - if data['disk_format'] in ('ami', 'aki', 'ari',): - container_format = data['disk_format'] - else: - container_format = 'bare' + meta = create_image_metadata(data) - meta = {'is_public': data['is_public'], - 'protected': data['protected'], - 'disk_format': data['disk_format'], - 'container_format': container_format, - 'min_disk': (data['minimum_disk'] or 0), - 'min_ram': (data['minimum_ram'] or 0), - 'name': data['name'], - 'properties': {}} - - if data['description']: - meta['properties']['description'] = data['description'] - if data.get('kernel'): - meta['properties']['kernel_id'] = data['kernel'] - if data.get('ramdisk'): - meta['properties']['ramdisk_id'] = data['ramdisk'] - if data.get('architecture'): - meta['properties']['architecture'] = data['architecture'] + # Add image source file or URL to metadata if (settings.HORIZON_IMAGES_ALLOW_UPLOAD and policy.check((("image", "upload_image"),), request) and data.get('image_file', None)): @@ -240,7 +259,7 @@ class CreateImageForm(forms.SelfHandlingForm): image = api.glance.image_create(request, **meta) messages.success(request, _('Your image %s has been queued for creation.') % - data['name']) + meta['name']) return image except Exception as e: msg = _('Unable to create new image') @@ -248,7 +267,7 @@ class CreateImageForm(forms.SelfHandlingForm): if hasattr(e, 'code') and e.code == 400: if "Invalid disk format" in e.details: msg = _('Unable to create new image: Invalid disk format ' - '%s for image.') % data['disk_format'] + '%s for image.') % meta['disk_format'] elif "Image name too long" in e.details: msg = _('Unable to create new image: Image name too long.') @@ -313,26 +332,7 @@ class UpdateImageForm(forms.SelfHandlingForm): def handle(self, request, data): image_id = data['image_id'] error_updating = _('Unable to update image "%s".') - - if data['disk_format'] in ['aki', 'ari', 'ami']: - container_format = data['disk_format'] - else: - container_format = 'bare' - - meta = {'is_public': data['public'], - 'protected': data['protected'], - 'disk_format': data['disk_format'], - 'container_format': container_format, - 'name': data['name'], - 'min_ram': (data['minimum_ram'] or 0), - 'min_disk': (data['minimum_disk'] or 0), - 'properties': {'description': data['description']}} - if data.get('kernel'): - meta['properties']['kernel_id'] = data['kernel'] - if data.get('ramdisk'): - meta['properties']['ramdisk_id'] = data['ramdisk'] - if data.get('architecture'): - meta['properties']['architecture'] = data['architecture'] + meta = create_image_metadata(data) # Ensure we do not delete properties that have already been # set on an image. meta['purge_props'] = False diff --git a/openstack_dashboard/dashboards/project/images/images/tables.py b/openstack_dashboard/dashboards/project/images/images/tables.py index 53742e6364..24ff3d0dd1 100644 --- a/openstack_dashboard/dashboards/project/images/images/tables.py +++ b/openstack_dashboard/dashboards/project/images/images/tables.py @@ -222,9 +222,12 @@ def get_format(image): # which will raise an error if you call upper() on it. if not format: return format - # Most image formats are untranslated acronyms, but raw is a word - # and should be translated if format == "raw": + if getattr(image, "container_format") == 'docker': + return pgettext_lazy("Image format for display in table", + u"Docker") + # Most image formats are untranslated acronyms, but raw is a word + # and should be translated return pgettext_lazy("Image format for display in table", u"Raw") return format.upper() diff --git a/openstack_dashboard/dashboards/project/images/images/tests.py b/openstack_dashboard/dashboards/project/images/images/tests.py index ac5cae2886..4e86468164 100644 --- a/openstack_dashboard/dashboards/project/images/images/tests.py +++ b/openstack_dashboard/dashboards/project/images/images/tests.py @@ -81,6 +81,31 @@ class CreateImageFormTests(test.TestCase): source_type_dict = dict(form.fields['source_type'].choices) self.assertNotIn('file', source_type_dict) + def test_create_image_metadata_docker(self): + form_data = { + 'name': u'Docker image', + 'description': u'Docker image test', + 'source_type': u'url', + 'image_url': u'/', + 'disk_format': u'docker', + 'architecture': u'x86-64', + 'minimum_disk': 15, + 'minimum_ram': 512, + 'is_public': False, + 'protected': False, + 'is_copying': False + } + meta = forms.create_image_metadata(form_data) + self.assertEqual(meta['disk_format'], 'raw') + self.assertEqual(meta['container_format'], 'docker') + self.assertIn('properties', meta) + self.assertNotIn('description', meta) + self.assertNotIn('architecture', meta) + self.assertEqual(meta['properties']['description'], + form_data['description']) + self.assertEqual(meta['properties']['architecture'], + form_data['architecture']) + class UpdateImageFormTests(test.TestCase): def test_is_format_field_editable(self): diff --git a/openstack_dashboard/dashboards/project/images/images/views.py b/openstack_dashboard/dashboards/project/images/images/views.py index 33213c2101..67ea6fff0f 100644 --- a/openstack_dashboard/dashboards/project/images/images/views.py +++ b/openstack_dashboard/dashboards/project/images/images/views.py @@ -79,17 +79,22 @@ class UpdateView(forms.ModalFormView): def get_initial(self): image = self.get_object() properties = getattr(image, 'properties', {}) - return {'image_id': self.kwargs['image_id'], + data = {'image_id': self.kwargs['image_id'], 'name': getattr(image, 'name', None) or image.id, 'description': properties.get('description', ''), 'kernel': properties.get('kernel_id', ''), 'ramdisk': properties.get('ramdisk_id', ''), 'architecture': properties.get('architecture', ''), - 'disk_format': getattr(image, 'disk_format', None), 'minimum_ram': getattr(image, 'min_ram', None), 'minimum_disk': getattr(image, 'min_disk', None), 'public': getattr(image, 'is_public', None), 'protected': getattr(image, 'protected', None)} + disk_format = getattr(image, 'disk_format', None) + if (disk_format == 'raw' and + getattr(image, 'container_format') == 'docker'): + disk_format = 'docker' + data['disk_format'] = disk_format + return data class DetailView(tabs.TabView): diff --git a/openstack_dashboard/local/local_settings.py.example b/openstack_dashboard/local/local_settings.py.example index d04935a94f..9ae6054fd4 100644 --- a/openstack_dashboard/local/local_settings.py.example +++ b/openstack_dashboard/local/local_settings.py.example @@ -265,6 +265,7 @@ OPENSTACK_NEUTRON_NETWORK = { # ('aki', _('AKI - Amazon Kernel Image')), # ('ami', _('AMI - Amazon Machine Image')), # ('ari', _('ARI - Amazon Ramdisk Image')), +# ('docker', _('Docker')), # ('iso', _('ISO - Optical Disk Image')), # ('ova', _('OVA - Open Virtual Appliance')), # ('qcow2', _('QCOW2 - QEMU Emulator')), diff --git a/openstack_dashboard/settings.py b/openstack_dashboard/settings.py index 5a22e45122..b37ac938d7 100644 --- a/openstack_dashboard/settings.py +++ b/openstack_dashboard/settings.py @@ -81,6 +81,7 @@ OPENSTACK_IMAGE_BACKEND = { ('aki', _('AKI - Amazon Kernel Image')), ('ami', _('AMI - Amazon Machine Image')), ('ari', _('ARI - Amazon Ramdisk Image')), + ('docker', _('Docker')), ('iso', _('ISO - Optical Disk Image')), ('ova', _('OVA - Open Virtual Appliance')), ('qcow2', _('QCOW2 - QEMU Emulator')),