Add support for downloading clouds.yaml files

python-openstackclient, shade, openstacksdk and Ansible's OpenStack
modules all support reading client config information from a file called
clouds.yaml instead of from environment variables set from openrc files.
Unfortunately, the only thing horizon currently offers for download is
old-style openrc files.

Add support for downloading clouds.yaml files.

Change-Id: I0611dd44524b746ad993bff7435ec8628a83a762
This commit is contained in:
Monty Taylor 2017-04-05 09:28:55 -05:00 committed by Akihiro Motoki
parent 21b25ed773
commit 6d315598aa
8 changed files with 206 additions and 4 deletions

View File

@ -796,6 +796,29 @@ When set, enables the instance action "Retrieve password" allowing password retr
from metadata service.
``OPENSTACK_CLOUDS_YAML_NAME``
------------------------------
.. versionadded:: 12.0.0(Pike)
Default: ``openstack``
The name of the entry to put into the user's clouds.yaml file.
``OPENSTACK_CLOUDS_YAML_PROFILE``
---------------------------------
.. versionadded:: 12.0.0(Pike)
Default: None
If set, the name of the `vendor profile`_ from `os-client-config`_.
.. _vendor profile: https://docs.openstack.org/developer/os-client-config/vendor-support.html
.. _os-client-config: https://docs.openstack.org/developer/os-client-config
``OPENSTACK_ENDPOINT_TYPE``
---------------------------

View File

@ -44,6 +44,14 @@ class DownloadEC2(tables.LinkAction):
return api.base.is_service_enabled(request, 'ec2')
class DownloadCloudsYaml(tables.LinkAction):
name = "download_clouds_yaml"
verbose_name = _("Download OpenStack clouds.yaml File")
verbose_name_plural = _("Download OpenStack clouds.yaml File")
icon = "download"
url = "horizon:project:api_access:clouds.yaml"
class DownloadOpenRC(tables.LinkAction):
name = "download_openrc"
verbose_name = _("Download OpenStack RC File v3")
@ -106,5 +114,6 @@ class EndpointsTable(tables.DataTable):
name = "endpoints"
verbose_name = _("API Endpoints")
multi_select = False
table_actions = (DownloadOpenRCv2, DownloadOpenRC, DownloadEC2,
table_actions = (DownloadCloudsYaml, DownloadOpenRCv2, DownloadOpenRC,
DownloadEC2,
ViewCredentials, RecreateCredentials)

View File

@ -0,0 +1,37 @@
# This is a clouds.yaml file, which can be used by OpenStack tools as a source
# of configuration on how to connect to a cloud. If this is your only cloud,
# just put this file in ~/.config/openstack/clouds.yaml and tools like
# python-openstackclient will just work with no further config. (You will need
# to add your password to the auth section)
# If you have more than one cloud account, add the cloud entry to the clouds
# section of your existing file and you can refer to them by name with
# OS_CLOUD={{ cloud_name }} or --os-cloud={{ cloud_name }}
clouds:
{{ cloud_name }}:
{% if profile %}
profile: {{ profile }}
{% endif %}
auth:
{% if not profile %}
auth_url: {{ auth_url }}
{% endif %}
username: "{{ user.username }}"
project_id: {{ tenant_id }}
project_name: "{{ tenant_name }}"
{% if user_domain_name %}
user_domain_name: "{{ user_domain_name }}"
{% endif %}
{% if not profile %}
{% if regions %}
regions:
{% for r in regions %}
- {{ r }}
{% endfor %}
{% else %}
{% if region %}
region_name: "{{ region }}"
{% endif %}
{% endif %}
interface: "{{ interface }}"
identity_api_version: {{ os_identity_api_version }}
{% endif %}

View File

@ -22,6 +22,8 @@ from openstack_dashboard.dashboards.project.api_access import views
urlpatterns = [
url(r'^$', views.IndexView.as_view(), name='index'),
url(r'^ec2/$', views.download_ec2_bundle, name='ec2'),
url(r'^clouds.yaml/$',
views.download_clouds_yaml_file, name='clouds.yaml'),
url(r'^openrc/$', views.download_rc_file, name='openrc'),
url(r'^openrcv2/$', views.download_rc_file_v2, name='openrcv2'),
url(r'^view_credentials/$', views.CredentialsView.as_view(),

View File

@ -17,6 +17,7 @@ import logging
import tempfile
import zipfile
from django.conf import settings
from django.core.urlresolvers import reverse_lazy
from django import http
from django import shortcuts
@ -143,14 +144,40 @@ def download_rc_file(request):
return _download_rc_file_for_template(request, context, template)
def _download_rc_file_for_template(request, context, template):
def download_clouds_yaml_file(request):
template = 'project/api_access/clouds.yaml.template'
context = _get_openrc_credentials(request)
context['cloud_name'] = getattr(
settings, "OPENSTACK_CLOUDS_YAML_NAME", 'openstack')
context['profile'] = getattr(
settings, "OPENSTACK_CLOUDS_YAML_PROFILE", None)
context['regions'] = [
region_tuple[1] for region_tuple in getattr(
settings, "AVAILABLE_REGIONS", [])
]
if utils.get_keystone_version() >= 3:
# make v3 specific changes
context['user_domain_name'] = request.user.user_domain_name
# sanity fix for removing v2.0 from the url if present
context['auth_url'], _ = utils.fix_auth_url_version_prefix(
context['auth_url'])
context['os_identity_api_version'] = 3
context['os_auth_version'] = 3
return _download_rc_file_for_template(request, context, template,
'clouds.yaml')
def _download_rc_file_for_template(request, context, template, filename=None):
try:
response = shortcuts.render(request,
template,
context,
content_type="text/plain")
tenant_name = context['tenant_name']
disposition = 'attachment; filename="%s-openrc.sh"' % tenant_name
if not filename:
filename = '%s-openrc.sh' % context['tenant_name']
disposition = 'attachment; filename="%s"' % filename
response['Content-Disposition'] = disposition.encode('utf-8')
response['Content-Length'] = str(len(response.content))
return response

View File

@ -239,6 +239,12 @@ LOCALE_PATHS = [
'openstack_dashboard/locale',
]
# Set OPENSTACK_CLOUDS_YAML_NAME to provide a nicer name for this cloud for
# the clouds.yaml file than "openstack".
OPENSTACK_CLOUDS_YAML_NAME = 'openstack'
# If this cloud has a vendor profile in os-client-config, put it's name here.
OPENSTACK_CLOUDS_YAML_PROFILE = ''
OPENSTACK_KEYSTONE_DEFAULT_ROLE = '_member_'
DEFAULT_EXCEPTION_REPORTER_FILTER = 'horizon.exceptions.HorizonReporterFilter'

View File

@ -13,6 +13,8 @@
# License for the specific language governing permissions and limitations
# under the License.
import yaml
from django import template
from django.template import loader
@ -95,3 +97,93 @@ class TemplateRenderTest(test.TestCase):
template.Context(context))
self.assertIn("OS_REGION_NAME=\"\"", out)
def test_clouds_yaml_set_region(self):
context = {
"cloud_name": "openstack",
"user": FakeUser(),
"tenant_id": "some-cool-id",
"auth_url": "http://example.com",
"tenant_name": "Tenant",
"region": "Colorado"}
out = yaml.load(loader.render_to_string(
'project/api_access/clouds.yaml.template',
context,
template.Context(context)))
self.assertIn('clouds', out)
self.assertIn('openstack', out['clouds'])
self.assertNotIn('profile', out['clouds']['openstack'])
self.assertEqual(
"http://example.com",
out['clouds']['openstack']['auth']['auth_url'])
self.assertEqual("Colorado", out['clouds']['openstack']['region_name'])
self.assertNotIn('regions', out['clouds']['openstack'])
def test_clouds_yaml_region_not_set(self):
context = {
"cloud_name": "openstack",
"user": FakeUser(),
"tenant_id": "some-cool-id",
"auth_url": "http://example.com",
"tenant_name": "Tenant"}
out = yaml.load(loader.render_to_string(
'project/api_access/clouds.yaml.template',
context,
template.Context(context)))
self.assertIn('clouds', out)
self.assertIn('openstack', out['clouds'])
self.assertNotIn('profile', out['clouds']['openstack'])
self.assertEqual(
"http://example.com",
out['clouds']['openstack']['auth']['auth_url'])
self.assertNotIn('region_name', out['clouds']['openstack'])
self.assertNotIn('regions', out['clouds']['openstack'])
def test_clouds_yaml_regions(self):
regions = ['region1', 'region2']
context = {
"cloud_name": "openstack",
"user": FakeUser(),
"tenant_id": "some-cool-id",
"auth_url": "http://example.com",
"tenant_name": "Tenant",
"regions": regions}
out = yaml.load(loader.render_to_string(
'project/api_access/clouds.yaml.template',
context,
template.Context(context)))
self.assertIn('clouds', out)
self.assertIn('openstack', out['clouds'])
self.assertNotIn('profile', out['clouds']['openstack'])
self.assertEqual(
"http://example.com",
out['clouds']['openstack']['auth']['auth_url'])
self.assertNotIn('region_name', out['clouds']['openstack'])
self.assertIn('regions', out['clouds']['openstack'])
self.assertEqual(regions, out['clouds']['openstack']['regions'])
def test_clouds_yaml_profile(self):
regions = ['region1', 'region2']
context = {
"cloud_name": "openstack",
"user": FakeUser(),
"profile": "example",
"tenant_id": "some-cool-id",
"auth_url": "http://example.com",
"tenant_name": "Tenant",
"regions": regions}
out = yaml.load(loader.render_to_string(
'project/api_access/clouds.yaml.template',
context,
template.Context(context)))
self.assertIn('clouds', out)
self.assertIn('openstack', out['clouds'])
self.assertIn('profile', out['clouds']['openstack'])
self.assertEqual('example', out['clouds']['openstack']['profile'])
self.assertNotIn('auth_url', out['clouds']['openstack']['auth'])
self.assertNotIn('region_name', out['clouds']['openstack'])
self.assertNotIn('regions', out['clouds']['openstack'])

View File

@ -0,0 +1,6 @@
---
features:
- Add support for horizon offering a clouds.yaml file
for download along with the openrc files. For more
information on clouds.yaml, see
https://docs.openstack.org/developer/os-client-config