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:
parent
21b25ed773
commit
6d315598aa
@ -796,6 +796,29 @@ When set, enables the instance action "Retrieve password" allowing password retr
|
|||||||
from metadata service.
|
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``
|
``OPENSTACK_ENDPOINT_TYPE``
|
||||||
---------------------------
|
---------------------------
|
||||||
|
|
||||||
|
@ -44,6 +44,14 @@ class DownloadEC2(tables.LinkAction):
|
|||||||
return api.base.is_service_enabled(request, 'ec2')
|
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):
|
class DownloadOpenRC(tables.LinkAction):
|
||||||
name = "download_openrc"
|
name = "download_openrc"
|
||||||
verbose_name = _("Download OpenStack RC File v3")
|
verbose_name = _("Download OpenStack RC File v3")
|
||||||
@ -106,5 +114,6 @@ class EndpointsTable(tables.DataTable):
|
|||||||
name = "endpoints"
|
name = "endpoints"
|
||||||
verbose_name = _("API Endpoints")
|
verbose_name = _("API Endpoints")
|
||||||
multi_select = False
|
multi_select = False
|
||||||
table_actions = (DownloadOpenRCv2, DownloadOpenRC, DownloadEC2,
|
table_actions = (DownloadCloudsYaml, DownloadOpenRCv2, DownloadOpenRC,
|
||||||
|
DownloadEC2,
|
||||||
ViewCredentials, RecreateCredentials)
|
ViewCredentials, RecreateCredentials)
|
||||||
|
@ -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 %}
|
@ -22,6 +22,8 @@ from openstack_dashboard.dashboards.project.api_access import views
|
|||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^$', views.IndexView.as_view(), name='index'),
|
url(r'^$', views.IndexView.as_view(), name='index'),
|
||||||
url(r'^ec2/$', views.download_ec2_bundle, name='ec2'),
|
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'^openrc/$', views.download_rc_file, name='openrc'),
|
||||||
url(r'^openrcv2/$', views.download_rc_file_v2, name='openrcv2'),
|
url(r'^openrcv2/$', views.download_rc_file_v2, name='openrcv2'),
|
||||||
url(r'^view_credentials/$', views.CredentialsView.as_view(),
|
url(r'^view_credentials/$', views.CredentialsView.as_view(),
|
||||||
|
@ -17,6 +17,7 @@ import logging
|
|||||||
import tempfile
|
import tempfile
|
||||||
import zipfile
|
import zipfile
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
from django.core.urlresolvers import reverse_lazy
|
from django.core.urlresolvers import reverse_lazy
|
||||||
from django import http
|
from django import http
|
||||||
from django import shortcuts
|
from django import shortcuts
|
||||||
@ -143,14 +144,40 @@ def download_rc_file(request):
|
|||||||
return _download_rc_file_for_template(request, context, template)
|
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:
|
try:
|
||||||
response = shortcuts.render(request,
|
response = shortcuts.render(request,
|
||||||
template,
|
template,
|
||||||
context,
|
context,
|
||||||
content_type="text/plain")
|
content_type="text/plain")
|
||||||
tenant_name = context['tenant_name']
|
if not filename:
|
||||||
disposition = 'attachment; filename="%s-openrc.sh"' % tenant_name
|
filename = '%s-openrc.sh' % context['tenant_name']
|
||||||
|
disposition = 'attachment; filename="%s"' % filename
|
||||||
response['Content-Disposition'] = disposition.encode('utf-8')
|
response['Content-Disposition'] = disposition.encode('utf-8')
|
||||||
response['Content-Length'] = str(len(response.content))
|
response['Content-Length'] = str(len(response.content))
|
||||||
return response
|
return response
|
||||||
|
@ -239,6 +239,12 @@ LOCALE_PATHS = [
|
|||||||
'openstack_dashboard/locale',
|
'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_'
|
OPENSTACK_KEYSTONE_DEFAULT_ROLE = '_member_'
|
||||||
|
|
||||||
DEFAULT_EXCEPTION_REPORTER_FILTER = 'horizon.exceptions.HorizonReporterFilter'
|
DEFAULT_EXCEPTION_REPORTER_FILTER = 'horizon.exceptions.HorizonReporterFilter'
|
||||||
|
@ -13,6 +13,8 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
|
import yaml
|
||||||
|
|
||||||
from django import template
|
from django import template
|
||||||
from django.template import loader
|
from django.template import loader
|
||||||
|
|
||||||
@ -95,3 +97,93 @@ class TemplateRenderTest(test.TestCase):
|
|||||||
template.Context(context))
|
template.Context(context))
|
||||||
|
|
||||||
self.assertIn("OS_REGION_NAME=\"\"", out)
|
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'])
|
||||||
|
6
releasenotes/notes/add-clouds-yaml-f72b9a0b7df2aa79.yaml
Normal file
6
releasenotes/notes/add-clouds-yaml-f72b9a0b7df2aa79.yaml
Normal 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
|
Loading…
x
Reference in New Issue
Block a user