Support resource sharing CLI

Change-Id: Id84054be6458039804b0ca702a727e6c696f003a
Implements: blueprint resource-sharing-cli
This commit is contained in:
Lingxian Kong 2016-02-12 15:54:28 +13:00
parent 8bd8592c83
commit 84e3e6e6ee
7 changed files with 522 additions and 1 deletions

View File

@ -21,6 +21,7 @@ from mistralclient.api.v2 import actions
from mistralclient.api.v2 import cron_triggers
from mistralclient.api.v2 import environments
from mistralclient.api.v2 import executions
from mistralclient.api.v2 import members
from mistralclient.api.v2 import services
from mistralclient.api.v2 import tasks
from mistralclient.api.v2 import workbooks
@ -74,6 +75,7 @@ class Client(object):
self.environments = environments.EnvironmentManager(self)
self.action_executions = action_executions.ActionExecutionManager(self)
self.services = services.ServiceManager(self)
self.members = members.MemberManager(self)
def authenticate(self, mistral_url=None, username=None, api_key=None,
project_name=None, auth_url=None, project_id=None,

View File

@ -0,0 +1,76 @@
# Copyright 2016 - Catalyst IT Limited
#
# 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 mistralclient.api import base
class Member(base.Resource):
resource_name = 'Member'
class MemberManager(base.ResourceManager):
resource_class = Member
def create(self, resource_id, resource_type, member_id):
self._ensure_not_empty(
resource_id=resource_id,
resource_type=resource_type,
member_id=member_id
)
data = {
'member_id': member_id,
}
url = '/%ss/%s/members' % (resource_type, resource_id)
return self._create(url, data)
def update(self, resource_id, resource_type, member_id='',
status='accepted'):
if not member_id:
member_id = self.client.http_client.project_id
url = '/%ss/%s/members/%s' % (resource_type, resource_id, member_id)
return self._update(url, {'status': status})
def list(self, resource_id, resource_type):
url = '/%ss/%s/members' % (resource_type, resource_id)
return self._list(url, response_key='members')
def get(self, resource_id, resource_type, member_id=None):
self._ensure_not_empty(
resource_id=resource_id,
resource_type=resource_type,
)
if not member_id:
member_id = self.client.http_client.project_id
url = '/%ss/%s/members/%s' % (resource_type, resource_id, member_id)
return self._get(url)
def delete(self, resource_id, resource_type, member_id):
self._ensure_not_empty(
resource_id=resource_id,
resource_type=resource_type,
member_id=member_id
)
url = '/%ss/%s/members/%s' % (resource_type, resource_id, member_id)
self._delete(url)

View File

@ -0,0 +1,232 @@
# Copyright 2016 - Catalyst IT Limited
#
# 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 cliff import command
from cliff import show
from mistralclient.commands.v2 import base
from mistralclient import exceptions
def format_list(member=None):
return format(member, lister=True)
def format(member=None, lister=False):
columns = (
'Resource ID',
'Resource Type',
'Resource Owner',
'Member ID',
'Status',
'Created at',
'Updated at'
)
if member:
data = (
member.resource_id,
member.resource_type,
member.project_id,
member.member_id,
member.status,
member.created_at,
member.updated_at or '<none>'
)
else:
data = (tuple('<none>' for _ in range(len(columns))),)
return columns, data
class List(base.MistralLister):
"""List all members."""
def _get_format_function(self):
return format_list
def get_parser(self, parsed_args):
parser = super(List, self).get_parser(parsed_args)
parser.add_argument(
'resource_id',
help='Resource id to be shared.'
)
parser.add_argument(
'resource_type',
help='Resource type.'
)
return parser
def _get_resources(self, parsed_args):
mistral_client = self.app.client_manager.workflow_engine
return mistral_client.members.list(
parsed_args.resource_id,
parsed_args.resource_type
)
class Get(show.ShowOne):
"""Show specific member information."""
def get_parser(self, prog_name):
parser = super(Get, self).get_parser(prog_name)
parser.add_argument(
'resource_id',
help='Resource ID to be shared.'
)
parser.add_argument(
'resource_type',
help='Resource type.'
)
parser.add_argument(
'-m',
'--member-id',
default='',
help='Project ID to whom the resource is shared to. No need to '
'provide this param if you are the resource member.'
)
return parser
def take_action(self, parsed_args):
mistral_client = self.app.client_manager.workflow_engine
member = mistral_client.members.get(
parsed_args.resource_id,
parsed_args.resource_type,
parsed_args.member_id,
)
return format(member)
class Create(show.ShowOne):
"""Shares a resource to another tenant."""
def get_parser(self, prog_name):
parser = super(Create, self).get_parser(prog_name)
parser.add_argument(
'resource_id',
help='Resource ID to be shared.'
)
parser.add_argument(
'resource_type',
help='Resource type.'
)
parser.add_argument(
'member_id',
help='Project ID to whom the resource is shared to.'
)
return parser
def take_action(self, parsed_args):
mistral_client = self.app.client_manager.workflow_engine
member = mistral_client.members.create(
parsed_args.resource_id,
parsed_args.resource_type,
parsed_args.member_id,
)
return format(member)
class Delete(command.Command):
"""Delete a resource sharing relationship."""
def get_parser(self, prog_name):
parser = super(Delete, self).get_parser(prog_name)
parser.add_argument(
'resource_id',
help='Resource ID to be shared.'
)
parser.add_argument(
'resource_type',
help='Resource type.'
)
parser.add_argument(
'member_id',
help='Project ID to whom the resource is shared to.'
)
return parser
def take_action(self, parsed_args):
mistral_client = self.app.client_manager.workflow_engine
try:
mistral_client.members.delete(
parsed_args.resource_id,
parsed_args.resource_type,
parsed_args.member_id,
)
print(
"Request to delete %s member %s has been accepted." %
(parsed_args.resource_type, parsed_args.member_id)
)
except Exception as e:
print(e)
error_msg = "Unable to delete the specified member."
raise exceptions.MistralClientException(error_msg)
class Update(show.ShowOne):
"""Update resource sharing status."""
def get_parser(self, prog_name):
parser = super(Update, self).get_parser(prog_name)
parser.add_argument(
'resource_id',
help='Resource ID to be shared.'
)
parser.add_argument(
'resource_type',
help='Resource type.'
)
parser.add_argument(
'-m',
'--member-id',
default='',
help='Project ID to whom the resource is shared to. No need to '
'provide this param if you are the resource member.'
)
parser.add_argument(
'-s',
'--status',
default='accepted',
choices=['pending', 'accepted', 'rejected'],
help='status of the sharing.'
)
return parser
def take_action(self, parsed_args):
mistral_client = self.app.client_manager.workflow_engine
member = mistral_client.members.update(
parsed_args.resource_id,
parsed_args.resource_type,
parsed_args.member_id,
status=parsed_args.status
)
return format(member)

View File

@ -25,6 +25,7 @@ import mistralclient.commands.v2.actions
import mistralclient.commands.v2.cron_triggers
import mistralclient.commands.v2.environments
import mistralclient.commands.v2.executions
import mistralclient.commands.v2.members
import mistralclient.commands.v2.services
import mistralclient.commands.v2.tasks
import mistralclient.commands.v2.workbooks
@ -408,7 +409,12 @@ class MistralShell(app.App):
mistralclient.commands.v2.cron_triggers.Create,
'cron-trigger-delete':
mistralclient.commands.v2.cron_triggers.Delete,
'service-list': mistralclient.commands.v2.services.List
'service-list': mistralclient.commands.v2.services.List,
'member-create': mistralclient.commands.v2.members.Create,
'member-delete': mistralclient.commands.v2.members.Delete,
'member-update': mistralclient.commands.v2.members.Update,
'member-list': mistralclient.commands.v2.members.List,
'member-get': mistralclient.commands.v2.members.Get,
}

View File

@ -31,3 +31,4 @@ class BaseClientV2Test(base.BaseClientTest):
self.action_executions = self._client.action_executions
self.actions = self._client.actions
self.services = self._client.services
self.members = self._client.members

View File

@ -0,0 +1,102 @@
# Copyright 2016 Catalyst IT Limited
#
# 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.
import mock
from mistralclient.api.v2 import members
from mistralclient.commands.v2 import members as member_cmd
from mistralclient.tests.unit import base
MEMBER_DICT = {
'id': '123',
'resource_id': '456',
'resource_type': 'workflow',
'project_id': '1111',
'member_id': '2222',
'status': 'pending',
'created_at': '1',
'updated_at': '1'
}
MEMBER = members.Member(mock, MEMBER_DICT)
class TestCLIWorkflowMembers(base.BaseCommandTest):
def test_create(self):
self.client.members.create.return_value = MEMBER
result = self.call(
member_cmd.Create,
app_args=[MEMBER_DICT['resource_id'], MEMBER_DICT['resource_type'],
MEMBER_DICT['member_id']]
)
self.assertEqual(
('456', 'workflow', '1111', '2222', 'pending', '1', '1'),
result[1]
)
def test_update(self):
self.client.members.update.return_value = MEMBER
result = self.call(
member_cmd.Update,
app_args=[MEMBER_DICT['resource_id'], MEMBER_DICT['resource_type'],
'-m', MEMBER_DICT['member_id']]
)
self.assertEqual(
('456', 'workflow', '1111', '2222', 'pending', '1', '1'),
result[1]
)
def test_list(self):
self.client.members.list.return_value = [MEMBER]
result = self.call(
member_cmd.List,
app_args=[MEMBER_DICT['resource_id'], MEMBER_DICT['resource_type']]
)
self.assertListEqual(
[('456', 'workflow', '1111', '2222', 'pending', '1', '1')],
result[1]
)
def test_get(self):
self.client.members.get.return_value = MEMBER
result = self.call(
member_cmd.Get,
app_args=[MEMBER_DICT['resource_id'], MEMBER_DICT['resource_type'],
'-m', MEMBER_DICT['member_id']]
)
self.assertEqual(
('456', 'workflow', '1111', '2222', 'pending', '1', '1'),
result[1]
)
def test_delete(self):
self.call(
member_cmd.Delete,
app_args=[MEMBER_DICT['resource_id'], MEMBER_DICT['resource_type'],
MEMBER_DICT['member_id']]
)
self.client.members.delete.assert_called_once_with(
MEMBER_DICT['resource_id'],
MEMBER_DICT['resource_type'],
MEMBER_DICT['member_id']
)

View File

@ -0,0 +1,102 @@
# Copyright 2016 Catalyst IT Limited
#
# 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.
import copy
import json
from mistralclient.tests.unit.v2 import base
MEMBER = {
'id': '123',
'resource_id': '456',
'resource_type': 'workflow',
'project_id': 'dc4ffdee54d74028b19b1b90e77aa84f',
'member_id': '04f61e967fa14cd49950ffe2319317ad',
'status': 'pending',
}
WORKFLOW_MEMBERS_URL = '/workflows/%s/members' % (MEMBER['resource_id'])
WORKFLOW_MEMBER_URL = '/workflows/%s/members/%s' % (
MEMBER['resource_id'], MEMBER['member_id']
)
class TestWorkflowMembers(base.BaseClientV2Test):
def test_create(self):
mock = self.mock_http_post(content=MEMBER)
mb = self.members.create(
MEMBER['resource_id'],
MEMBER['resource_type'],
MEMBER['member_id']
)
self.assertIsNotNone(mb)
mock.assert_called_once_with(
WORKFLOW_MEMBERS_URL,
json.dumps({'member_id': MEMBER['member_id']})
)
def test_update(self):
updated_member = copy.copy(MEMBER)
updated_member['status'] = 'accepted'
mock = self.mock_http_put(content=updated_member)
mb = self.members.update(
MEMBER['resource_id'],
MEMBER['resource_type'],
MEMBER['member_id']
)
self.assertIsNotNone(mb)
mock.assert_called_once_with(
WORKFLOW_MEMBER_URL,
json.dumps({"status": "accepted"})
)
def test_list(self):
mock = self.mock_http_get(content={'members': [MEMBER]})
mbs = self.members.list(MEMBER['resource_id'], MEMBER['resource_type'])
self.assertEqual(1, len(mbs))
mock.assert_called_once_with(WORKFLOW_MEMBERS_URL)
def test_get(self):
mock = self.mock_http_get(content=MEMBER)
mb = self.members.get(
MEMBER['resource_id'],
MEMBER['resource_type'],
MEMBER['member_id']
)
self.assertIsNotNone(mb)
mock.assert_called_once_with(WORKFLOW_MEMBER_URL)
def test_delete(self):
mock = self.mock_http_delete(status_code=204)
self.members.delete(
MEMBER['resource_id'],
MEMBER['resource_type'],
MEMBER['member_id']
)
mock.assert_called_once_with(WORKFLOW_MEMBER_URL)