summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIgor Malinovskiy <u.glide@gmail.com>2020-04-26 20:25:50 +0300
committerMichael Johnson <johnsomor@gmail.com>2023-02-13 20:51:32 +0000
commitbc39d23ff5ff45e7669cb4be9d2c28b9242cf9d9 (patch)
tree936488d5239b9ef1a7890c06006c38ec42bd3bc9
parent483e0d16c6b357a610634ad5f7db9ab61b41d353 (diff)
downloadpython-designateclient-bc39d23ff5ff45e7669cb4be9d2c28b9242cf9d9.tar.gz
Add shared zone commands5.2.0
Co-Authored-By: Michael Johnson <johnsomor@gmail.com> Change-Id: Iea92371176d9126205384624a18a9097acb3daef Partial-Bug: #1714088 Depends-On: https://review.opendev.org/#/c/726334/
-rw-r--r--designateclient/functionaltests/client.py21
-rw-r--r--designateclient/functionaltests/v2/fixtures.py22
-rw-r--r--designateclient/functionaltests/v2/test_shared_zone.py73
-rw-r--r--designateclient/tests/v2/test_zones.py82
-rw-r--r--designateclient/v2/cli/zones.py133
-rw-r--r--designateclient/v2/client.py2
-rw-r--r--designateclient/v2/zones.py36
-rw-r--r--releasenotes/notes/Add-shared-zones-support-4be565f3d1c6356c.yaml6
-rw-r--r--setup.cfg5
9 files changed, 376 insertions, 4 deletions
diff --git a/designateclient/functionaltests/client.py b/designateclient/functionaltests/client.py
index 4d1d0b0..6edf377 100644
--- a/designateclient/functionaltests/client.py
+++ b/designateclient/functionaltests/client.py
@@ -348,9 +348,28 @@ class BlacklistCommands(object):
return self.parsed_cmd(cmd, FieldValueModel, *args, **kwargs)
+class SharedZoneCommands(object):
+
+ def shared_zone_show(self, zone_id, shared_zone_id, *args, **kwargs):
+ cmd = 'zone share show {0} {1}'.format(zone_id, shared_zone_id)
+ return self.parsed_cmd(cmd, FieldValueModel, *args, **kwargs)
+
+ def shared_zone_list(self, zone_id, *args, **kwargs):
+ cmd = 'zone share list {0}'.format(zone_id)
+ return self.parsed_cmd(cmd, ListModel, *args, **kwargs)
+
+ def share_zone(self, zone_id, target_project_id, *args, **kwargs):
+ cmd = 'zone share create {0} {1}'.format(zone_id, target_project_id)
+ return self.parsed_cmd(cmd, FieldValueModel, *args, **kwargs)
+
+ def unshare_zone(self, zone_id, shared_zone_id, *args, **kwargs):
+ cmd = 'zone share delete {0} {1}'.format(zone_id, shared_zone_id)
+ return self.parsed_cmd(cmd, FieldValueModel, *args, **kwargs)
+
+
class DesignateCLI(base.CLIClient, ZoneCommands, ZoneTransferCommands,
ZoneExportCommands, ZoneImportCommands, RecordsetCommands,
- TLDCommands, BlacklistCommands):
+ TLDCommands, BlacklistCommands, SharedZoneCommands):
# instantiate this once to minimize requests to keystone
_CLIENTS = None
diff --git a/designateclient/functionaltests/v2/fixtures.py b/designateclient/functionaltests/v2/fixtures.py
index cc5a83b..c72a18b 100644
--- a/designateclient/functionaltests/v2/fixtures.py
+++ b/designateclient/functionaltests/v2/fixtures.py
@@ -228,3 +228,25 @@ class BlacklistFixture(BaseFixture):
client.zone_blacklist_delete(blacklist_id)
except CommandFailed:
pass
+
+
+class SharedZoneFixture(BaseFixture):
+ """See DesignateCLI.recordset_create for __init__ args"""
+
+ def __init__(self, zone, *args, **kwargs):
+ super(SharedZoneFixture, self).__init__(*args, **kwargs)
+ self.zone = zone
+
+ def _setUp(self):
+ super(SharedZoneFixture, self)._setUp()
+ self.zone_share = self.client.zone_share(zone_id=self.zone.id,
+ *self.args, **self.kwargs)
+ self.addCleanup(self.cleanup_shared_zone, self.client, self.zone.id,
+ self.zone_share.id)
+
+ @classmethod
+ def cleanup_shared_zone(cls, client, zone_id, shared_zone_id):
+ try:
+ client.unshare_zone(zone_id, shared_zone_id)
+ except CommandFailed:
+ pass
diff --git a/designateclient/functionaltests/v2/test_shared_zone.py b/designateclient/functionaltests/v2/test_shared_zone.py
new file mode 100644
index 0000000..0e8b392
--- /dev/null
+++ b/designateclient/functionaltests/v2/test_shared_zone.py
@@ -0,0 +1,73 @@
+"""
+ Copyright 2020 Cloudification GmbH. All rights reserved.
+
+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 designateclient.functionaltests.base import BaseDesignateTest
+from designateclient.functionaltests.client import DesignateCLI
+from designateclient.functionaltests.datagen import random_zone_name
+from designateclient.functionaltests.v2.fixtures import SharedZoneFixture
+from designateclient.functionaltests.v2.fixtures import ZoneFixture
+
+
+class TestSharedZone(BaseDesignateTest):
+
+ def setUp(self):
+ super(TestSharedZone, self).setUp()
+ self.ensure_tld_exists('com')
+ fixture = self.useFixture(ZoneFixture(
+ name=random_zone_name(),
+ email='test@example.com',
+ ))
+ self.zone = fixture.zone
+ self.target_client = DesignateCLI.as_user('alt')
+
+ def test_list_shared_zones(self):
+ shared_zone = self.useFixture(SharedZoneFixture(
+ zone_id=self.zone.id,
+ target_tenant_id=self.target_client.project_id
+ )).zone_share
+
+ shared_zones = self.clients.shared_zone_list(self.zone.id)
+ self.assertGreater(len(shared_zones), 0)
+ self.assertTrue(self._is_entity_in_list(shared_zone, shared_zones))
+
+ def test_share_and_show_shared_zone(self):
+ shared_zone = self.useFixture(SharedZoneFixture(
+ zone_id=self.zone.id,
+ target_tenant_id=self.target_client.project_id
+ )).zone_share
+
+ fetched_shared_zone = self.clients.shared_zone_show(self.zone.id,
+ shared_zone.id)
+
+ self.assertEqual(
+ shared_zone.created_at, fetched_shared_zone.created_at)
+ self.assertEqual(shared_zone.id, fetched_shared_zone.id)
+ self.assertEqual(
+ shared_zone.project_id, fetched_shared_zone.project_id)
+ self.assertEqual(shared_zone.zone_id, fetched_shared_zone.zone_id)
+
+ def test_unshare_zone(self):
+ shared_zone = self.useFixture(SharedZoneFixture(
+ zone_id=self.zone.id,
+ target_tenant_id=self.target_client.project_id
+ )).zone_share
+
+ shared_zones = self.clients.shared_zone_list(self.zone.id)
+ self.assertTrue(self._is_entity_in_list(shared_zone, shared_zones))
+
+ self.clients.unshare_zone(self.zone.id, shared_zone.id)
+
+ shared_zones = self.clients.shared_zone_list(self.zone.id)
+ self.assertFalse(self._is_entity_in_list(shared_zone, shared_zones))
diff --git a/designateclient/tests/v2/test_zones.py b/designateclient/tests/v2/test_zones.py
index 5470be7..63ee81e 100644
--- a/designateclient/tests/v2/test_zones.py
+++ b/designateclient/tests/v2/test_zones.py
@@ -116,7 +116,19 @@ class TestZones(v2.APIV2TestCase, v2.CrudMixin):
self.stub_entity("DELETE", id=ref["id"])
self.client.zones.delete(ref["id"])
+
+ self.assertRequestBodyIs(None)
+ self.assertRequestHeaderEqual('X-Designate-Delete-Shares', None)
+
+ def test_delete_with_delete_shares(self):
+ ref = self.new_ref()
+
+ self.stub_entity("DELETE", id=ref["id"])
+
+ self.client.zones.delete(ref["id"], delete_shares=True)
+
self.assertRequestBodyIs(None)
+ self.assertRequestHeaderEqual('X-Designate-Delete-Shares', 'true')
def test_task_abandon(self):
ref = self.new_ref()
@@ -380,3 +392,73 @@ class TestZoneImports(v2.APIV2TestCase, v2.CrudMixin):
self.client.zone_imports.delete(ref["id"])
self.assertRequestBodyIs(None)
+
+
+class TestZoneShared(v2.APIV2TestCase, v2.CrudMixin):
+ def setUp(self):
+ super(TestZoneShared, self).setUp()
+ self.zone_id = str(uuid.uuid4())
+ self.target_project_id = str(uuid.uuid4())
+ self.project_id = str(uuid.uuid4())
+ self.created_at = time.strftime("%c")
+ self.updated_at = time.strftime("%c")
+
+ def new_ref(self, **kwargs):
+ ref = super(TestZoneShared, self).new_ref(**kwargs)
+ ref.setdefault("zone_id", self.zone_id)
+ ref.setdefault("target_project_id", self.target_project_id)
+ ref.setdefault("project_id", self.project_id)
+ ref.setdefault("created_at", self.created_at)
+ ref.setdefault("updated_at", self.updated_at)
+ return ref
+
+ def test_share_a_zone(self):
+ json_body = {"target_project_id": self.target_project_id}
+
+ expected = self.new_ref()
+
+ self.stub_entity('POST', parts=['zones', self.zone_id, 'shares'],
+ entity=expected, json=json_body)
+
+ response = self.client.zone_share.create(self.zone_id,
+ self.target_project_id)
+
+ self.assertRequestBodyIs(json=json_body)
+ self.assertEqual(expected, response)
+
+ def test_get_zone_share(self):
+ expected = self.new_ref()
+
+ parts = ["zones", self.zone_id, "shares"]
+ self.stub_entity("GET", parts=parts, entity=expected,
+ id=expected["id"])
+
+ response = self.client.zone_share.get(self.zone_id, expected["id"])
+
+ self.assertRequestBodyIs(None)
+ self.assertEqual(expected, response)
+
+ def test_list_zone_shares(self):
+ items = [
+ self.new_ref(),
+ self.new_ref()
+ ]
+
+ parts = ["zones", self.zone_id, "shares"]
+ self.stub_entity('GET', parts=parts, entity={"shared_zones": items})
+
+ listed = self.client.zone_share.list(self.zone_id)
+
+ self.assertList(items, listed)
+ self.assertQueryStringIs("")
+
+ def test_delete_zone_share(self):
+ ref = self.new_ref()
+
+ parts = ["zones", self.zone_id, "shares", ref["id"]]
+ self.stub_url('DELETE', parts=parts)
+
+ response = self.client.zone_share.delete(self.zone_id, ref["id"])
+
+ self.assertRequestBodyIs(None)
+ self.assertEqual('', response)
diff --git a/designateclient/v2/cli/zones.py b/designateclient/v2/cli/zones.py
index f326232..24f64de 100644
--- a/designateclient/v2/cli/zones.py
+++ b/designateclient/v2/cli/zones.py
@@ -241,6 +241,10 @@ class DeleteZoneCommand(command.ShowOne):
parser.add_argument('id', help="Zone ID")
+ parser.add_argument('--delete-shares', default=False,
+ action='store_true',
+ help='Delete existing zone shares. Default: False')
+
common.add_all_common_options(parser)
common.add_hard_delete_option(parser)
@@ -250,7 +254,13 @@ class DeleteZoneCommand(command.ShowOne):
client = self.app.client_manager.dns
common.set_all_common_headers(client, parsed_args)
- data = client.zones.delete(parsed_args.id)
+ delete_shares = False
+ if (hasattr(parsed_args, 'delete_shares') and
+ parsed_args.delete_shares is not None and
+ isinstance(parsed_args.delete_shares, bool)):
+ delete_shares = parsed_args.delete_shares
+
+ data = client.zones.delete(parsed_args.id, delete_shares=delete_shares)
LOG.info('Zone %s was deleted', parsed_args.id)
_format_zone(data)
@@ -724,3 +734,124 @@ class DeleteZoneImportCommand(command.Command):
client.zone_imports.delete(parsed_args.zone_import_id)
LOG.info('Zone Import %s was deleted', parsed_args.zone_import_id)
+
+
+class ShareZoneCommand(command.ShowOne):
+ """Share a Zone"""
+
+ def get_parser(self, prog_name):
+ parser = super(ShareZoneCommand, self).get_parser(
+ prog_name)
+
+ common.add_all_common_options(parser)
+
+ parser.add_argument('zone', help='The zone name or ID to share.')
+ parser.add_argument('target_project_id',
+ help='Target project ID to share the zone with.')
+
+ return parser
+
+ def take_action(self, parsed_args):
+ client = self.app.client_manager.dns
+ common.set_all_common_headers(client, parsed_args)
+
+ data = client.zone_share.create(
+ parsed_args.zone,
+ parsed_args.target_project_id
+ )
+
+ LOG.info('Zone %s was shared', data['id'])
+
+ data.pop('links', None)
+
+ return self.dict2columns(data)
+
+
+class ListSharedZonesCommand(command.Lister):
+ """List Zone Shares"""
+
+ columns = [
+ 'id',
+ 'zone_id',
+ 'target_project_id',
+ ]
+
+ def get_parser(self, prog_name):
+ parser = super(ListSharedZonesCommand, self).get_parser(
+ prog_name)
+
+ common.add_all_common_options(parser)
+
+ parser.add_argument('zone', help='The zone name or ID to share.')
+
+ parser.add_argument('--target-project-id',
+ help='The target project ID to filter on.',
+ required=False)
+ return parser
+
+ def take_action(self, parsed_args):
+ client = self.app.client_manager.dns
+ common.set_all_common_headers(client, parsed_args)
+
+ criterion = {}
+ if parsed_args.target_project_id is not None:
+ criterion['target_project_id'] = parsed_args.target_project_id
+
+ data = get_all(client.zone_share.list, criterion=criterion,
+ args=[parsed_args.zone])
+
+ cols = list(self.columns)
+
+ if client.session.all_projects:
+ cols.insert(1, 'project_id')
+
+ return cols, (utils.get_item_properties(s, cols) for s in data)
+
+
+class ShowSharedZoneCommand(command.ShowOne):
+ """Show Zone Share Details"""
+
+ def get_parser(self, prog_name):
+ parser = super(ShowSharedZoneCommand, self).get_parser(prog_name)
+
+ parser.add_argument('zone', help='The zone name or ID to share.')
+ parser.add_argument('shared_zone_id',
+ help='The zone share ID to show.')
+
+ common.add_all_common_options(parser)
+
+ return parser
+
+ def take_action(self, parsed_args):
+ client = self.app.client_manager.dns
+ common.set_all_common_headers(client, parsed_args)
+
+ data = client.zone_share.get(parsed_args.zone,
+ parsed_args.shared_zone_id)
+ data.pop('links', None)
+
+ return self.dict2columns(data)
+
+
+class DeleteSharedZoneCommand(command.Command):
+ """Delete a Zone Share"""
+
+ def get_parser(self, prog_name):
+ parser = super(DeleteSharedZoneCommand, self).get_parser(
+ prog_name)
+
+ parser.add_argument('zone', help='The zone name or ID to share.')
+ parser.add_argument('shared_zone_id',
+ help='The zone share ID to delete.')
+
+ common.add_all_common_options(parser)
+
+ return parser
+
+ def take_action(self, parsed_args):
+ client = self.app.client_manager.dns
+ common.set_all_common_headers(client, parsed_args)
+
+ client.zone_share.delete(parsed_args.zone, parsed_args.shared_zone_id)
+
+ LOG.info('Shared Zone %s was deleted', parsed_args.shared_zone_id)
diff --git a/designateclient/v2/client.py b/designateclient/v2/client.py
index 4463506..053275c 100644
--- a/designateclient/v2/client.py
+++ b/designateclient/v2/client.py
@@ -29,6 +29,7 @@ from designateclient.v2.tsigkeys import TSIGKeysController
from designateclient.v2.zones import ZoneController
from designateclient.v2.zones import ZoneExportsController
from designateclient.v2.zones import ZoneImportsController
+from designateclient.v2.zones import ZoneShareController
from designateclient.v2.zones import ZoneTransfersController
from designateclient import version
from oslo_utils import importutils
@@ -151,6 +152,7 @@ class Client(object):
self.zone_transfers = ZoneTransfersController(self)
self.zone_exports = ZoneExportsController(self)
self.zone_imports = ZoneImportsController(self)
+ self.zone_share = ZoneShareController(self)
self.pools = PoolController(self)
self.quotas = QuotasController(self)
self.tsigkeys = TSIGKeysController(self)
diff --git a/designateclient/v2/zones.py b/designateclient/v2/zones.py
index 7e862a7..209d4f0 100644
--- a/designateclient/v2/zones.py
+++ b/designateclient/v2/zones.py
@@ -62,12 +62,18 @@ class ZoneController(V2Controller):
return self._patch(url, data=values)
- def delete(self, zone):
+ def delete(self, zone, delete_shares=False):
zone = v2_utils.resolve_by_name(self.list, zone)
url = self.build_url('/zones/%s' % zone)
- return self._delete(url)
+ if delete_shares:
+ headers = {'X-Designate-Delete-Shares': 'true'}
+ _resp, body = self.client.session.delete(url, headers=headers)
+ else:
+ _resp, body = self.client.session.delete(url)
+
+ return body
def abandon(self, zone):
zone = v2_utils.resolve_by_name(self.list, zone)
@@ -166,3 +172,29 @@ class ZoneImportsController(V2Controller):
def delete(self, zone_import_id):
return self._delete('/zones/tasks/imports/%s' % zone_import_id)
+
+
+class ZoneShareController(V2Controller):
+ def create(self, zone, target_project_id):
+ zone_id = v2_utils.resolve_by_name(self.client.zones.list, zone)
+
+ data = {"target_project_id": target_project_id}
+
+ return self._post(f'/zones/{zone_id}/shares', data=data)
+
+ def list(self, zone, criterion=None, marker=None, limit=None):
+ zone_id = v2_utils.resolve_by_name(self.client.zones.list, zone)
+ url = self.build_url(f'/zones/{zone_id}/shares',
+ criterion, marker, limit)
+
+ return self._get(url, response_key='shared_zones')
+
+ def delete(self, zone, shared_zone_id):
+ zone_id = v2_utils.resolve_by_name(self.client.zones.list, zone)
+
+ return self._delete(f'/zones/{zone_id}/shares/{shared_zone_id}')
+
+ def get(self, zone, shared_zone_id):
+ zone_id = v2_utils.resolve_by_name(self.client.zones.list, zone)
+
+ return self._get(f'/zones/{zone_id}/shares/{shared_zone_id}')
diff --git a/releasenotes/notes/Add-shared-zones-support-4be565f3d1c6356c.yaml b/releasenotes/notes/Add-shared-zones-support-4be565f3d1c6356c.yaml
new file mode 100644
index 0000000..7d810d1
--- /dev/null
+++ b/releasenotes/notes/Add-shared-zones-support-4be565f3d1c6356c.yaml
@@ -0,0 +1,6 @@
+---
+features:
+ - Adds zone share commands to support sharing zones with additional projects.
+ - Adds a ``--delete-shares`` option to zone delete to delete existing zone
+ shares along with the zone. Without this option, you cannot delete a zone
+ that has been shared with other projects.
diff --git a/setup.cfg b/setup.cfg
index f6e2414..8642922 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -74,6 +74,11 @@ openstack.dns.v2 =
zone_transfer_accept_list = designateclient.v2.cli.zones:ListTransferAcceptsCommand
zone_transfer_accept_show = designateclient.v2.cli.zones:ShowTransferAcceptCommand
+ zone_share_create = designateclient.v2.cli.zones:ShareZoneCommand
+ zone_share_list = designateclient.v2.cli.zones:ListSharedZonesCommand
+ zone_share_show = designateclient.v2.cli.zones:ShowSharedZoneCommand
+ zone_share_delete = designateclient.v2.cli.zones:DeleteSharedZoneCommand
+
recordset_create = designateclient.v2.cli.recordsets:CreateRecordSetCommand
recordset_list = designateclient.v2.cli.recordsets:ListRecordSetsCommand
recordset_show = designateclient.v2.cli.recordsets:ShowRecordSetCommand