Merge "Migrate passthrough to openstacksdk"
This commit is contained in:
commit
eaa6b7c3ba
@ -13,4 +13,4 @@
|
|||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
"""REST API for Horizon dashboard Javascript code.
|
"""REST API for Horizon dashboard Javascript code.
|
||||||
"""
|
"""
|
||||||
from . import passthrough # noqa
|
from . import designate # noqa
|
||||||
|
272
designatedashboard/api/rest/designate.py
Normal file
272
designatedashboard/api/rest/designate.py
Normal file
@ -0,0 +1,272 @@
|
|||||||
|
# Copyright (c) 2023 Binero
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
from designatedashboard.sdk_connection import get_sdk_connection
|
||||||
|
from django.views import generic
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from openstack_dashboard.api.rest import urls
|
||||||
|
from openstack_dashboard.api.rest import utils as rest_utils
|
||||||
|
|
||||||
|
from openstack.dns.v2 import floating_ip as _fip
|
||||||
|
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def _sdk_object_to_list(object):
|
||||||
|
"""Converts an SDK generator object to a list of dictionaries.
|
||||||
|
|
||||||
|
:param object: SDK generator object
|
||||||
|
:returns: List of dictionaries
|
||||||
|
"""
|
||||||
|
result_list = []
|
||||||
|
for item in object:
|
||||||
|
result_list.append(item.to_dict())
|
||||||
|
return result_list
|
||||||
|
|
||||||
|
|
||||||
|
def create_zone(request):
|
||||||
|
"""Create zone."""
|
||||||
|
data = request.DATA
|
||||||
|
|
||||||
|
conn = get_sdk_connection(request)
|
||||||
|
build_kwargs = dict(
|
||||||
|
name=data['name'],
|
||||||
|
email=data['email'],
|
||||||
|
type=data['type'],
|
||||||
|
)
|
||||||
|
if data.get('description', None):
|
||||||
|
build_kwargs['description'] = data['description']
|
||||||
|
if data.get('ttl', None):
|
||||||
|
build_kwargs['ttl'] = data['ttl']
|
||||||
|
if data.get('masters', None):
|
||||||
|
build_kwargs['masters'] = data['masters']
|
||||||
|
|
||||||
|
zone = conn.dns.create_zone(**build_kwargs)
|
||||||
|
return zone.to_dict()
|
||||||
|
|
||||||
|
|
||||||
|
def update_zone(request, **kwargs):
|
||||||
|
"""Update zone."""
|
||||||
|
data = request.DATA
|
||||||
|
zone_id = kwargs.get('zone_id')
|
||||||
|
|
||||||
|
conn = get_sdk_connection(request)
|
||||||
|
build_kwargs = dict(
|
||||||
|
email=data['email'],
|
||||||
|
description=data['description'],
|
||||||
|
ttl=data['ttl'],
|
||||||
|
)
|
||||||
|
zone = conn.dns.update_zone(
|
||||||
|
zone_id, **build_kwargs)
|
||||||
|
return zone.to_dict()
|
||||||
|
|
||||||
|
|
||||||
|
@urls.register
|
||||||
|
class Zones(generic.View):
|
||||||
|
"""API for zones."""
|
||||||
|
|
||||||
|
url_regex = r'dns/v2/zones/$'
|
||||||
|
|
||||||
|
@rest_utils.ajax()
|
||||||
|
def get(self, request):
|
||||||
|
"""List zones for current project."""
|
||||||
|
conn = get_sdk_connection(request)
|
||||||
|
zones = _sdk_object_to_list(conn.dns.zones())
|
||||||
|
return {'zones': zones}
|
||||||
|
|
||||||
|
@rest_utils.ajax(data_required=True)
|
||||||
|
def post(self, request):
|
||||||
|
"""Create zone."""
|
||||||
|
return create_zone(request)
|
||||||
|
|
||||||
|
|
||||||
|
@urls.register
|
||||||
|
class Zone(generic.View):
|
||||||
|
"""API for zone."""
|
||||||
|
url_regex = r'dns/v2/zones/(?P<zone_id>[^/]+)/$'
|
||||||
|
|
||||||
|
@rest_utils.ajax()
|
||||||
|
def get(self, request, zone_id):
|
||||||
|
"""Get zone."""
|
||||||
|
conn = get_sdk_connection(request)
|
||||||
|
zone = conn.dns.find_zone(zone_id)
|
||||||
|
return zone.to_dict()
|
||||||
|
|
||||||
|
@rest_utils.ajax(data_required=True)
|
||||||
|
def patch(self, request, zone_id):
|
||||||
|
"""Edit zone."""
|
||||||
|
kwargs = {'zone_id': zone_id}
|
||||||
|
update_zone(request, **kwargs)
|
||||||
|
|
||||||
|
@rest_utils.ajax()
|
||||||
|
def delete(self, request, zone_id):
|
||||||
|
"""Delete zone."""
|
||||||
|
conn = get_sdk_connection(request)
|
||||||
|
conn.dns.delete_zone(zone_id, ignore_missing=True)
|
||||||
|
|
||||||
|
|
||||||
|
def create_recordset(request, **kwargs):
|
||||||
|
"""Create recordset."""
|
||||||
|
data = request.DATA
|
||||||
|
zone_id = kwargs.get('zone_id')
|
||||||
|
|
||||||
|
conn = get_sdk_connection(request)
|
||||||
|
build_kwargs = dict(
|
||||||
|
name=data['name'],
|
||||||
|
type=data['type'],
|
||||||
|
ttl=data['ttl'],
|
||||||
|
records=data['records'],
|
||||||
|
)
|
||||||
|
if data.get('description', None):
|
||||||
|
build_kwargs['description'] = data['description']
|
||||||
|
|
||||||
|
rs = conn.dns.create_recordset(
|
||||||
|
zone_id, **build_kwargs)
|
||||||
|
return rs.to_dict()
|
||||||
|
|
||||||
|
|
||||||
|
def update_recordset(request, **kwargs):
|
||||||
|
"""Update recordset."""
|
||||||
|
data = request.DATA
|
||||||
|
zone_id = kwargs.get('zone_id')
|
||||||
|
rs_id = kwargs.get('rs_id')
|
||||||
|
|
||||||
|
conn = get_sdk_connection(request)
|
||||||
|
|
||||||
|
build_kwargs = dict()
|
||||||
|
if data.get('description', None):
|
||||||
|
build_kwargs['description'] = data['description']
|
||||||
|
if data.get('ttl', None):
|
||||||
|
build_kwargs['ttl'] = data['ttl']
|
||||||
|
if data.get('records', None):
|
||||||
|
build_kwargs['records'] = data['records']
|
||||||
|
|
||||||
|
build_kwargs['zone_id'] = zone_id
|
||||||
|
rs = conn.dns.update_recordset(
|
||||||
|
rs_id, **build_kwargs)
|
||||||
|
return rs.to_dict()
|
||||||
|
|
||||||
|
|
||||||
|
def _populate_zone_id(items, zone_id):
|
||||||
|
for item in items:
|
||||||
|
item['zone_id'] = zone_id
|
||||||
|
return items
|
||||||
|
|
||||||
|
|
||||||
|
@urls.register
|
||||||
|
class RecordSets(generic.View):
|
||||||
|
"""API for recordsets."""
|
||||||
|
url_regex = r'dns/v2/zones/(?P<zone_id>[^/]+)/recordsets/$'
|
||||||
|
|
||||||
|
@rest_utils.ajax()
|
||||||
|
def get(self, request, zone_id):
|
||||||
|
"""Get recordsets."""
|
||||||
|
conn = get_sdk_connection(request)
|
||||||
|
rsets = _sdk_object_to_list(conn.dns.recordsets(zone_id))
|
||||||
|
return {'recordsets': _populate_zone_id(rsets, zone_id)}
|
||||||
|
|
||||||
|
@rest_utils.ajax(data_required=True)
|
||||||
|
def post(self, request, zone_id):
|
||||||
|
"""Create recordset."""
|
||||||
|
kwargs = {'zone_id': zone_id}
|
||||||
|
return create_recordset(request, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
@urls.register
|
||||||
|
class RecordSet(generic.View):
|
||||||
|
"""API for recordset."""
|
||||||
|
url_regex = r'dns/v2/zones/(?P<zone_id>[^/]+)/recordsets/(?P<rs_id>[^/]+)/$' # noqa
|
||||||
|
|
||||||
|
@rest_utils.ajax()
|
||||||
|
def get(self, request, zone_id, rs_id):
|
||||||
|
"""Get recordset."""
|
||||||
|
conn = get_sdk_connection(request)
|
||||||
|
rs = conn.dns.get_recordset(rs_id, zone_id)
|
||||||
|
rs_dict = rs.to_dict()
|
||||||
|
rs_dict['zone_id'] = zone_id
|
||||||
|
return rs_dict
|
||||||
|
|
||||||
|
@rest_utils.ajax(data_required=True)
|
||||||
|
def put(self, request, zone_id, rs_id):
|
||||||
|
"""Edit recordset."""
|
||||||
|
kwargs = {'zone_id': zone_id, 'rs_id': rs_id}
|
||||||
|
update_recordset(request, **kwargs)
|
||||||
|
|
||||||
|
@rest_utils.ajax()
|
||||||
|
def delete(self, request, zone_id, rs_id):
|
||||||
|
"""Delete recordset."""
|
||||||
|
conn = get_sdk_connection(request)
|
||||||
|
conn.dns.delete_recordset(rs_id, zone_id, ignore_missing=True)
|
||||||
|
|
||||||
|
|
||||||
|
@urls.register
|
||||||
|
class DnsFloatingIps(generic.View):
|
||||||
|
"""API for floatingips."""
|
||||||
|
url_regex = r'dns/v2/reverse/floatingips/$'
|
||||||
|
|
||||||
|
@rest_utils.ajax()
|
||||||
|
def get(self, request):
|
||||||
|
"""Get floatingips."""
|
||||||
|
conn = get_sdk_connection(request)
|
||||||
|
fips = _sdk_object_to_list(conn.dns.floating_ips())
|
||||||
|
return {'floatingips': fips}
|
||||||
|
|
||||||
|
|
||||||
|
def update_dns_floatingip(request, **kwargs):
|
||||||
|
"""Update recordset."""
|
||||||
|
data = request.DATA
|
||||||
|
fip_id = kwargs.get('fip_id')
|
||||||
|
|
||||||
|
conn = get_sdk_connection(request)
|
||||||
|
|
||||||
|
build_kwargs = dict(
|
||||||
|
ptrdname=data['ptrdname'],
|
||||||
|
)
|
||||||
|
if data.get('description', None):
|
||||||
|
build_kwargs['description'] = data['description']
|
||||||
|
if data.get('ttl', None):
|
||||||
|
build_kwargs['ttl'] = data['ttl']
|
||||||
|
|
||||||
|
# TODO(tobias-urdin): Bug in openstacksdk
|
||||||
|
# https://review.opendev.org/c/openstack/openstacksdk/+/903879
|
||||||
|
obj = conn.dns._get_resource(
|
||||||
|
_fip.FloatingIP, fip_id, **build_kwargs)
|
||||||
|
obj.resource_key = None
|
||||||
|
has_body = True
|
||||||
|
if build_kwargs['ptrdname'] is None:
|
||||||
|
has_body = False
|
||||||
|
fip = obj.commit(conn.dns, has_body=has_body)
|
||||||
|
|
||||||
|
return fip.to_dict()
|
||||||
|
|
||||||
|
|
||||||
|
@urls.register
|
||||||
|
class DnsFloatingIp(generic.View):
|
||||||
|
"""API for dns floatingip."""
|
||||||
|
url_regex = r'dns/v2/reverse/floatingips/(?P<fip_id>[^/]+)/$'
|
||||||
|
|
||||||
|
@rest_utils.ajax()
|
||||||
|
def get(self, request, fip_id):
|
||||||
|
"""Get floatingip."""
|
||||||
|
conn = get_sdk_connection(request)
|
||||||
|
fip = conn.dns.get_floating_ip(fip_id)
|
||||||
|
return fip.to_dict()
|
||||||
|
|
||||||
|
@rest_utils.ajax(data_required=True)
|
||||||
|
def patch(self, request, fip_id):
|
||||||
|
"""Edit floatingip."""
|
||||||
|
kwargs = {'fip_id': fip_id}
|
||||||
|
update_dns_floatingip(request, **kwargs)
|
@ -1,119 +0,0 @@
|
|||||||
# Copyright 2016, Hewlett Packard Enterprise Development, LP
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
"""API for the passthrough service.
|
|
||||||
"""
|
|
||||||
from django.conf import settings
|
|
||||||
from django.views import generic
|
|
||||||
import functools
|
|
||||||
import logging
|
|
||||||
import requests
|
|
||||||
from requests.exceptions import HTTPError
|
|
||||||
|
|
||||||
from horizon import exceptions
|
|
||||||
from openstack_dashboard.api import base
|
|
||||||
from openstack_dashboard.api.rest import urls
|
|
||||||
from openstack_dashboard.api.rest import utils as rest_utils
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
def _passthrough_request(request_method, url,
|
|
||||||
request, data=None, params=None):
|
|
||||||
"""Makes a request to the appropriate service API with an optional payload.
|
|
||||||
|
|
||||||
Should set any necessary auth headers and SSL parameters.
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Set verify if a CACERT is set and SSL_NO_VERIFY isn't True
|
|
||||||
verify = getattr(settings, 'OPENSTACK_SSL_CACERT', None)
|
|
||||||
if getattr(settings, 'OPENSTACK_SSL_NO_VERIFY', False):
|
|
||||||
verify = False
|
|
||||||
|
|
||||||
service_url = _get_service_url(request, 'dns')
|
|
||||||
request_url = '{}{}'.format(
|
|
||||||
service_url,
|
|
||||||
url if service_url.endswith('/') else ('/' + url)
|
|
||||||
)
|
|
||||||
|
|
||||||
response = request_method(
|
|
||||||
request_url,
|
|
||||||
headers={'X-Auth-Token': request.user.token.id},
|
|
||||||
json=data,
|
|
||||||
verify=verify,
|
|
||||||
params=params
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
|
||||||
response.raise_for_status()
|
|
||||||
except HTTPError as e:
|
|
||||||
LOG.debug(e.response.content)
|
|
||||||
for error in rest_utils.http_errors:
|
|
||||||
if (e.response.status_code == getattr(error, 'status_code', 0) and
|
|
||||||
exceptions.HorizonException in error.__bases__):
|
|
||||||
raise error
|
|
||||||
raise
|
|
||||||
|
|
||||||
return response
|
|
||||||
|
|
||||||
|
|
||||||
# Create some convenience partial functions
|
|
||||||
passthrough_get = functools.partial(_passthrough_request, requests.get)
|
|
||||||
passthrough_post = functools.partial(_passthrough_request, requests.post)
|
|
||||||
passthrough_put = functools.partial(_passthrough_request, requests.put)
|
|
||||||
passthrough_patch = functools.partial(_passthrough_request, requests.patch)
|
|
||||||
passthrough_delete = functools.partial(_passthrough_request, requests.delete)
|
|
||||||
|
|
||||||
|
|
||||||
def _get_service_url(request, service):
|
|
||||||
"""Get service's URL from keystone; allow an override in settings"""
|
|
||||||
service_url = getattr(settings, service.upper() + '_URL', None)
|
|
||||||
try:
|
|
||||||
service_url = base.url_for(request, service)
|
|
||||||
except exceptions.ServiceCatalogException:
|
|
||||||
pass
|
|
||||||
# Currently the keystone endpoint is http://host:port/
|
|
||||||
# without the version.
|
|
||||||
return service_url
|
|
||||||
|
|
||||||
|
|
||||||
@urls.register
|
|
||||||
class Passthrough(generic.View):
|
|
||||||
"""Pass-through API for executing service requests.
|
|
||||||
|
|
||||||
Horizon only adds auth and CORS proxying.
|
|
||||||
"""
|
|
||||||
url_regex = r'dns/(?P<path>.+)$'
|
|
||||||
|
|
||||||
@rest_utils.ajax()
|
|
||||||
def get(self, request, path):
|
|
||||||
return passthrough_get(path, request).json()
|
|
||||||
|
|
||||||
@rest_utils.ajax()
|
|
||||||
def post(self, request, path):
|
|
||||||
data = dict(request.DATA) if request.DATA else {}
|
|
||||||
return passthrough_post(path, request, data).json()
|
|
||||||
|
|
||||||
@rest_utils.ajax()
|
|
||||||
def put(self, request, path):
|
|
||||||
data = dict(request.DATA) if request.DATA else {}
|
|
||||||
return passthrough_put(path, request, data).json()
|
|
||||||
|
|
||||||
@rest_utils.ajax()
|
|
||||||
def patch(self, request, path):
|
|
||||||
data = dict(request.DATA) if request.DATA else {}
|
|
||||||
return passthrough_patch(path, request, data).json()
|
|
||||||
|
|
||||||
@rest_utils.ajax()
|
|
||||||
def delete(self, request, path):
|
|
||||||
return passthrough_delete(path, request).json()
|
|
50
designatedashboard/sdk_connection.py
Normal file
50
designatedashboard/sdk_connection.py
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
# Copyright Red Hat
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
import designatedashboard
|
||||||
|
from openstack import config as occ
|
||||||
|
from openstack import connection
|
||||||
|
|
||||||
|
|
||||||
|
def get_sdk_connection(request):
|
||||||
|
"""Creates an SDK connection based on the request.
|
||||||
|
|
||||||
|
:param request: Django request object
|
||||||
|
:returns: SDK connection object
|
||||||
|
"""
|
||||||
|
# NOTE(mordred) Nothing says love like two inverted booleans
|
||||||
|
# The config setting is NO_VERIFY which is, in fact, insecure.
|
||||||
|
# get_one_cloud wants verify, so we pass 'not insecure' to verify.
|
||||||
|
insecure = getattr(settings, 'OPENSTACK_SSL_NO_VERIFY', False)
|
||||||
|
cacert = getattr(settings, 'OPENSTACK_SSL_CACERT', None)
|
||||||
|
# Pass interface to honor 'OPENSTACK_ENDPOINT_TYPE'
|
||||||
|
interface = getattr(settings, 'OPENSTACK_ENDPOINT_TYPE', 'publicURL')
|
||||||
|
# Pass load_yaml_config as this is a Django service with its own config
|
||||||
|
# and we don't want to accidentally pick up a clouds.yaml file. We want to
|
||||||
|
# use the settings we're passing in.
|
||||||
|
cloud_config = occ.OpenStackConfig(load_yaml_config=False).get_one_cloud(
|
||||||
|
verify=not insecure,
|
||||||
|
cacert=cacert,
|
||||||
|
interface=interface,
|
||||||
|
region_name=request.user.services_region,
|
||||||
|
auth_type='token',
|
||||||
|
auth=dict(
|
||||||
|
project_id=request.user.project_id,
|
||||||
|
project_domain_id=request.user.domain_id,
|
||||||
|
auth_token=request.user.token.unscoped_token,
|
||||||
|
auth_url=request.user.endpoint),
|
||||||
|
app_name='designate-dashboard',
|
||||||
|
app_version=designatedashboard.__version__)
|
||||||
|
return connection.from_config(cloud_config=cloud_config)
|
@ -61,7 +61,7 @@
|
|||||||
*/
|
*/
|
||||||
function list(params) {
|
function list(params) {
|
||||||
var config = params ? {params: params} : {};
|
var config = params ? {params: params} : {};
|
||||||
return httpService.get(apiPassthroughUrl + 'v2/reverse/floatingips', config)
|
return httpService.get(apiPassthroughUrl + 'v2/reverse/floatingips/', config)
|
||||||
.catch(function () {
|
.catch(function () {
|
||||||
toastService.add('error', gettext('Unable to retrieve the floating ip PTRs.'));
|
toastService.add('error', gettext('Unable to retrieve the floating ip PTRs.'));
|
||||||
});
|
});
|
||||||
@ -69,7 +69,7 @@
|
|||||||
|
|
||||||
function get(id, params) {
|
function get(id, params) {
|
||||||
var config = params ? {params: params} : {};
|
var config = params ? {params: params} : {};
|
||||||
return httpService.get(apiPassthroughUrl + 'v2/reverse/floatingips/' + id, config)
|
return httpService.get(apiPassthroughUrl + 'v2/reverse/floatingips/' + id + '/', config)
|
||||||
.catch(function () {
|
.catch(function () {
|
||||||
toastService.add('error', gettext('Unable to get the floating ip PTR ' + id));
|
toastService.add('error', gettext('Unable to get the floating ip PTR ' + id));
|
||||||
});
|
});
|
||||||
@ -95,7 +95,7 @@
|
|||||||
ttl: data.ttl
|
ttl: data.ttl
|
||||||
};
|
};
|
||||||
return httpService.patch(
|
return httpService.patch(
|
||||||
apiPassthroughUrl + 'v2/reverse/floatingips/' + floatingIpID, apiData)
|
apiPassthroughUrl + 'v2/reverse/floatingips/' + floatingIpID + '/', apiData)
|
||||||
.catch(function () {
|
.catch(function () {
|
||||||
toastService.add('error', gettext('Unable to set the floating IP PTR record.'));
|
toastService.add('error', gettext('Unable to set the floating IP PTR record.'));
|
||||||
});
|
});
|
||||||
|
@ -130,7 +130,7 @@
|
|||||||
records: data.records
|
records: data.records
|
||||||
};
|
};
|
||||||
return httpService.put(
|
return httpService.put(
|
||||||
apiPassthroughUrl + 'v2/zones/' + zoneId + '/recordsets/' + recordSetId, apiData)
|
apiPassthroughUrl + 'v2/zones/' + zoneId + '/recordsets/' + recordSetId + '/', apiData)
|
||||||
.catch(function () {
|
.catch(function () {
|
||||||
toastService.add('error', gettext('Unable to update the record set.'));
|
toastService.add('error', gettext('Unable to update the record set.'));
|
||||||
});
|
});
|
||||||
|
4
releasenotes/notes/openstacksdk-11483491f9978bd1.yaml
Normal file
4
releasenotes/notes/openstacksdk-11483491f9978bd1.yaml
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
upgrade:
|
||||||
|
- |
|
||||||
|
The designate dashboard now needs openstacksdk
|
@ -8,3 +8,4 @@
|
|||||||
pbr!=2.1.0,>=2.0.0 # Apache-2.0
|
pbr!=2.1.0,>=2.0.0 # Apache-2.0
|
||||||
|
|
||||||
horizon>=17.1.0 # Apache-2.0
|
horizon>=17.1.0 # Apache-2.0
|
||||||
|
openstacksdk>=0.62.0 # Apache-2.0
|
||||||
|
Loading…
Reference in New Issue
Block a user