Merge "Add project tags functionality"
This commit is contained in:
commit
d6761f0936
doc/source/cli/command-objects
openstackclient
releasenotes/notes
@ -19,6 +19,7 @@ Create new project
|
|||||||
[--enable | --disable]
|
[--enable | --disable]
|
||||||
[--property <key=value>]
|
[--property <key=value>]
|
||||||
[--or-show]
|
[--or-show]
|
||||||
|
[--tag <tag>]
|
||||||
<name>
|
<name>
|
||||||
|
|
||||||
.. option:: --domain <domain>
|
.. option:: --domain <domain>
|
||||||
@ -56,6 +57,13 @@ Create new project
|
|||||||
|
|
||||||
If the project already exists return the existing project data and do not fail.
|
If the project already exists return the existing project data and do not fail.
|
||||||
|
|
||||||
|
.. option:: --tag
|
||||||
|
|
||||||
|
Add a tag to the project
|
||||||
|
(repeat option to set multiple tags)
|
||||||
|
|
||||||
|
.. versionadded:: 3
|
||||||
|
|
||||||
.. _project_create-name:
|
.. _project_create-name:
|
||||||
.. describe:: <name>
|
.. describe:: <name>
|
||||||
|
|
||||||
@ -98,6 +106,8 @@ List projects
|
|||||||
[--my-projects]
|
[--my-projects]
|
||||||
[--long]
|
[--long]
|
||||||
[--sort <key>[:<direction>,<key>:<direction>,..]]
|
[--sort <key>[:<direction>,<key>:<direction>,..]]
|
||||||
|
[--tags <tag>[,<tag>,...]] [--tags-any <tag>[,<tag>,...]]
|
||||||
|
[--not-tags <tag>[,<tag>,...]] [--not-tags-any <tag>[,<tag>,...]]
|
||||||
|
|
||||||
.. option:: --domain <domain>
|
.. option:: --domain <domain>
|
||||||
|
|
||||||
@ -127,6 +137,30 @@ List projects
|
|||||||
multiple keys and directions can be specified --sort
|
multiple keys and directions can be specified --sort
|
||||||
<key>[:<direction>,<key>:<direction>,..]
|
<key>[:<direction>,<key>:<direction>,..]
|
||||||
|
|
||||||
|
.. option:: --tags <tag>[,<tag>,...]
|
||||||
|
|
||||||
|
List projects which have all given tag(s)
|
||||||
|
|
||||||
|
.. versionadded:: 3
|
||||||
|
|
||||||
|
.. option:: --tags-any <tag>[,<tag>,...]
|
||||||
|
|
||||||
|
List projects which have any given tag(s)
|
||||||
|
|
||||||
|
.. versionadded:: 3
|
||||||
|
|
||||||
|
.. option:: --not-tags <tag>[,<tag>,...]
|
||||||
|
|
||||||
|
Exclude projects which have all given tag(s)
|
||||||
|
|
||||||
|
.. versionadded:: 3
|
||||||
|
|
||||||
|
.. option:: --not-tags-any <tag>[,<tag>,...]
|
||||||
|
|
||||||
|
Exclude projects which have any given tag(s)
|
||||||
|
|
||||||
|
.. versionadded:: 3
|
||||||
|
|
||||||
project set
|
project set
|
||||||
-----------
|
-----------
|
||||||
|
|
||||||
@ -141,6 +175,7 @@ Set project properties
|
|||||||
[--description <description>]
|
[--description <description>]
|
||||||
[--enable | --disable]
|
[--enable | --disable]
|
||||||
[--property <key=value>]
|
[--property <key=value>]
|
||||||
|
[--tag <tag> | --clear-tags | --remove-tags <tag>]
|
||||||
<project>
|
<project>
|
||||||
|
|
||||||
.. option:: --name <name>
|
.. option:: --name <name>
|
||||||
|
@ -26,7 +26,7 @@ import six
|
|||||||
|
|
||||||
from openstackclient.i18n import _
|
from openstackclient.i18n import _
|
||||||
from openstackclient.identity import common
|
from openstackclient.identity import common
|
||||||
|
from openstackclient.identity.v3 import tag
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -79,6 +79,7 @@ class CreateProject(command.ShowOne):
|
|||||||
action='store_true',
|
action='store_true',
|
||||||
help=_('Return existing project'),
|
help=_('Return existing project'),
|
||||||
)
|
)
|
||||||
|
tag.add_tag_option_to_parser_for_create(parser, _('project'))
|
||||||
return parser
|
return parser
|
||||||
|
|
||||||
def take_action(self, parsed_args):
|
def take_action(self, parsed_args):
|
||||||
@ -102,6 +103,7 @@ class CreateProject(command.ShowOne):
|
|||||||
kwargs = {}
|
kwargs = {}
|
||||||
if parsed_args.property:
|
if parsed_args.property:
|
||||||
kwargs = parsed_args.property.copy()
|
kwargs = parsed_args.property.copy()
|
||||||
|
kwargs['tags'] = list(set(parsed_args.tags))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
project = identity_client.projects.create(
|
project = identity_client.projects.create(
|
||||||
@ -207,6 +209,7 @@ class ListProject(command.Lister):
|
|||||||
'(default: asc), repeat this option to specify multiple '
|
'(default: asc), repeat this option to specify multiple '
|
||||||
'keys and directions.'),
|
'keys and directions.'),
|
||||||
)
|
)
|
||||||
|
tag.add_tag_filtering_option_to_parser(parser, _('projects'))
|
||||||
return parser
|
return parser
|
||||||
|
|
||||||
def take_action(self, parsed_args):
|
def take_action(self, parsed_args):
|
||||||
@ -234,6 +237,8 @@ class ListProject(command.Lister):
|
|||||||
|
|
||||||
kwargs['user'] = user_id
|
kwargs['user'] = user_id
|
||||||
|
|
||||||
|
tag.get_tag_filtering_args(parsed_args, kwargs)
|
||||||
|
|
||||||
if parsed_args.my_projects:
|
if parsed_args.my_projects:
|
||||||
# NOTE(adriant): my-projects supersedes all the other filters.
|
# NOTE(adriant): my-projects supersedes all the other filters.
|
||||||
kwargs = {'user': self.app.client_manager.auth_ref.user_id}
|
kwargs = {'user': self.app.client_manager.auth_ref.user_id}
|
||||||
@ -303,6 +308,7 @@ class SetProject(command.Command):
|
|||||||
help=_('Set a property on <project> '
|
help=_('Set a property on <project> '
|
||||||
'(repeat option to set multiple properties)'),
|
'(repeat option to set multiple properties)'),
|
||||||
)
|
)
|
||||||
|
tag.add_tag_option_to_parser_for_set(parser, _('project'))
|
||||||
return parser
|
return parser
|
||||||
|
|
||||||
def take_action(self, parsed_args):
|
def take_action(self, parsed_args):
|
||||||
@ -323,6 +329,7 @@ class SetProject(command.Command):
|
|||||||
kwargs['enabled'] = False
|
kwargs['enabled'] = False
|
||||||
if parsed_args.property:
|
if parsed_args.property:
|
||||||
kwargs.update(parsed_args.property)
|
kwargs.update(parsed_args.property)
|
||||||
|
tag.update_tags_in_args(parsed_args, project, kwargs)
|
||||||
|
|
||||||
identity_client.projects.update(project.id, **kwargs)
|
identity_client.projects.update(project.id, **kwargs)
|
||||||
|
|
||||||
|
116
openstackclient/identity/v3/tag.py
Normal file
116
openstackclient/identity/v3/tag.py
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
# 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 argparse
|
||||||
|
|
||||||
|
from openstackclient.i18n import _
|
||||||
|
|
||||||
|
|
||||||
|
class _CommaListAction(argparse.Action):
|
||||||
|
|
||||||
|
def __call__(self, parser, namespace, values, option_string=None):
|
||||||
|
setattr(namespace, self.dest, values.split(','))
|
||||||
|
|
||||||
|
|
||||||
|
def add_tag_filtering_option_to_parser(parser, collection_name):
|
||||||
|
parser.add_argument(
|
||||||
|
'--tags',
|
||||||
|
metavar='<tag>[,<tag>,...]',
|
||||||
|
action=_CommaListAction,
|
||||||
|
help=_('List %s which have all given tag(s) '
|
||||||
|
'(Comma-separated list of tags)') % collection_name
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--tags-any',
|
||||||
|
metavar='<tag>[,<tag>,...]',
|
||||||
|
action=_CommaListAction,
|
||||||
|
help=_('List %s which have any given tag(s) '
|
||||||
|
'(Comma-separated list of tags)') % collection_name
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--not-tags',
|
||||||
|
metavar='<tag>[,<tag>,...]',
|
||||||
|
action=_CommaListAction,
|
||||||
|
help=_('Exclude %s which have all given tag(s) '
|
||||||
|
'(Comma-separated list of tags)') % collection_name
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--not-tags-any',
|
||||||
|
metavar='<tag>[,<tag>,...]',
|
||||||
|
action=_CommaListAction,
|
||||||
|
help=_('Exclude %s which have any given tag(s) '
|
||||||
|
'(Comma-separated list of tags)') % collection_name
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_tag_filtering_args(parsed_args, args):
|
||||||
|
if parsed_args.tags:
|
||||||
|
args['tags'] = ','.join(parsed_args.tags)
|
||||||
|
if parsed_args.tags_any:
|
||||||
|
args['tags-any'] = ','.join(parsed_args.tags_any)
|
||||||
|
if parsed_args.not_tags:
|
||||||
|
args['not-tags'] = ','.join(parsed_args.not_tags)
|
||||||
|
if parsed_args.not_tags_any:
|
||||||
|
args['not-tags-any'] = ','.join(parsed_args.not_tags_any)
|
||||||
|
|
||||||
|
|
||||||
|
def add_tag_option_to_parser_for_create(parser, resource_name):
|
||||||
|
tag_group = parser.add_mutually_exclusive_group()
|
||||||
|
tag_group.add_argument(
|
||||||
|
'--tag',
|
||||||
|
action='append',
|
||||||
|
dest='tags',
|
||||||
|
metavar='<tag>',
|
||||||
|
default=[],
|
||||||
|
help=_('Tag to be added to the %s '
|
||||||
|
'(repeat option to set multiple tags)') % resource_name
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def add_tag_option_to_parser_for_set(parser, resource_name):
|
||||||
|
parser.add_argument(
|
||||||
|
'--tag',
|
||||||
|
action='append',
|
||||||
|
dest='tags',
|
||||||
|
metavar='<tag>',
|
||||||
|
default=[],
|
||||||
|
help=_('Tag to be added to the %s '
|
||||||
|
'(repeat option to set multiple tags)') % resource_name
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--clear-tags',
|
||||||
|
action='store_true',
|
||||||
|
help=_('Clear tags associated with the %s. Specify '
|
||||||
|
'both --tag and --clear-tags to overwrite '
|
||||||
|
'current tags') % resource_name
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--remove-tag',
|
||||||
|
metavar='<tag>',
|
||||||
|
default=[],
|
||||||
|
help=_('Tag to be deleted from the %s '
|
||||||
|
'(repeat option to delete multiple tags)') % resource_name
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def update_tags_in_args(parsed_args, obj, args):
|
||||||
|
if parsed_args.clear_tags:
|
||||||
|
args['tags'] = []
|
||||||
|
obj.tags = []
|
||||||
|
if parsed_args.remove_tag:
|
||||||
|
if parsed_args.remove_tag in obj.tags:
|
||||||
|
obj.tags.remove(parsed_args.remove_tag)
|
||||||
|
args['tags'] = list(set(obj.tags))
|
||||||
|
return
|
||||||
|
if parsed_args.tags:
|
||||||
|
args['tags'] = list(set(obj.tags).union(
|
||||||
|
set(parsed_args.tags)))
|
@ -34,6 +34,7 @@ DOMAIN = {
|
|||||||
'name': domain_name,
|
'name': domain_name,
|
||||||
'description': domain_description,
|
'description': domain_description,
|
||||||
'enabled': True,
|
'enabled': True,
|
||||||
|
'tags': [],
|
||||||
'links': base_url + 'domains/' + domain_id,
|
'links': base_url + 'domains/' + domain_id,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -115,6 +116,7 @@ PROJECT = {
|
|||||||
'description': project_description,
|
'description': project_description,
|
||||||
'enabled': True,
|
'enabled': True,
|
||||||
'domain_id': domain_id,
|
'domain_id': domain_id,
|
||||||
|
'tags': [],
|
||||||
'links': base_url + 'projects/' + project_id,
|
'links': base_url + 'projects/' + project_id,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -124,6 +126,7 @@ PROJECT_2 = {
|
|||||||
'description': project_description + 'plus four more',
|
'description': project_description + 'plus four more',
|
||||||
'enabled': True,
|
'enabled': True,
|
||||||
'domain_id': domain_id,
|
'domain_id': domain_id,
|
||||||
|
'tags': [],
|
||||||
'links': base_url + 'projects/' + project_id,
|
'links': base_url + 'projects/' + project_id,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -145,6 +148,7 @@ PROJECT_WITH_PARENT = {
|
|||||||
'enabled': True,
|
'enabled': True,
|
||||||
'domain_id': domain_id,
|
'domain_id': domain_id,
|
||||||
'parent_id': project_id,
|
'parent_id': project_id,
|
||||||
|
'tags': [],
|
||||||
'links': base_url + 'projects/' + (project_id + '-with-parent'),
|
'links': base_url + 'projects/' + (project_id + '-with-parent'),
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -155,6 +159,7 @@ PROJECT_WITH_GRANDPARENT = {
|
|||||||
'enabled': True,
|
'enabled': True,
|
||||||
'domain_id': domain_id,
|
'domain_id': domain_id,
|
||||||
'parent_id': PROJECT_WITH_PARENT['id'],
|
'parent_id': PROJECT_WITH_PARENT['id'],
|
||||||
|
'tags': [],
|
||||||
'links': base_url + 'projects/' + (project_id + '-with-grandparent'),
|
'links': base_url + 'projects/' + (project_id + '-with-grandparent'),
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -619,6 +624,7 @@ class FakeProject(object):
|
|||||||
'is_domain': False,
|
'is_domain': False,
|
||||||
'domain_id': 'domain-id-' + uuid.uuid4().hex,
|
'domain_id': 'domain-id-' + uuid.uuid4().hex,
|
||||||
'parent_id': 'parent-id-' + uuid.uuid4().hex,
|
'parent_id': 'parent-id-' + uuid.uuid4().hex,
|
||||||
|
'tags': [],
|
||||||
'links': 'links-' + uuid.uuid4().hex,
|
'links': 'links-' + uuid.uuid4().hex,
|
||||||
}
|
}
|
||||||
project_info.update(attrs)
|
project_info.update(attrs)
|
||||||
@ -666,6 +672,7 @@ class FakeDomain(object):
|
|||||||
'name': 'domain-name-' + uuid.uuid4().hex,
|
'name': 'domain-name-' + uuid.uuid4().hex,
|
||||||
'description': 'domain-description-' + uuid.uuid4().hex,
|
'description': 'domain-description-' + uuid.uuid4().hex,
|
||||||
'enabled': True,
|
'enabled': True,
|
||||||
|
'tags': [],
|
||||||
'links': 'links-' + uuid.uuid4().hex,
|
'links': 'links-' + uuid.uuid4().hex,
|
||||||
}
|
}
|
||||||
domain_info.update(attrs)
|
domain_info.update(attrs)
|
||||||
|
@ -31,6 +31,7 @@ class TestDomainCreate(TestDomain):
|
|||||||
'enabled',
|
'enabled',
|
||||||
'id',
|
'id',
|
||||||
'name',
|
'name',
|
||||||
|
'tags'
|
||||||
)
|
)
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
@ -43,6 +44,7 @@ class TestDomainCreate(TestDomain):
|
|||||||
True,
|
True,
|
||||||
self.domain.id,
|
self.domain.id,
|
||||||
self.domain.name,
|
self.domain.name,
|
||||||
|
self.domain.tags
|
||||||
)
|
)
|
||||||
|
|
||||||
# Get the command object to test
|
# Get the command object to test
|
||||||
@ -390,12 +392,13 @@ class TestDomainShow(TestDomain):
|
|||||||
self.domain.id,
|
self.domain.id,
|
||||||
)
|
)
|
||||||
|
|
||||||
collist = ('description', 'enabled', 'id', 'name')
|
collist = ('description', 'enabled', 'id', 'name', 'tags')
|
||||||
self.assertEqual(collist, columns)
|
self.assertEqual(collist, columns)
|
||||||
datalist = (
|
datalist = (
|
||||||
self.domain.description,
|
self.domain.description,
|
||||||
True,
|
True,
|
||||||
self.domain.id,
|
self.domain.id,
|
||||||
self.domain.name,
|
self.domain.name,
|
||||||
|
self.domain.tags
|
||||||
)
|
)
|
||||||
self.assertEqual(datalist, data)
|
self.assertEqual(datalist, data)
|
||||||
|
@ -50,6 +50,7 @@ class TestProjectCreate(TestProject):
|
|||||||
'is_domain',
|
'is_domain',
|
||||||
'name',
|
'name',
|
||||||
'parent_id',
|
'parent_id',
|
||||||
|
'tags'
|
||||||
)
|
)
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
@ -67,6 +68,7 @@ class TestProjectCreate(TestProject):
|
|||||||
False,
|
False,
|
||||||
self.project.name,
|
self.project.name,
|
||||||
self.project.parent_id,
|
self.project.parent_id,
|
||||||
|
self.project.tags
|
||||||
)
|
)
|
||||||
# Get the command object to test
|
# Get the command object to test
|
||||||
self.cmd = project.CreateProject(self.app, None)
|
self.cmd = project.CreateProject(self.app, None)
|
||||||
@ -80,6 +82,7 @@ class TestProjectCreate(TestProject):
|
|||||||
('enable', False),
|
('enable', False),
|
||||||
('disable', False),
|
('disable', False),
|
||||||
('name', self.project.name),
|
('name', self.project.name),
|
||||||
|
('tags', [])
|
||||||
]
|
]
|
||||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||||
|
|
||||||
@ -95,6 +98,7 @@ class TestProjectCreate(TestProject):
|
|||||||
'description': None,
|
'description': None,
|
||||||
'enabled': True,
|
'enabled': True,
|
||||||
'parent': None,
|
'parent': None,
|
||||||
|
'tags': []
|
||||||
}
|
}
|
||||||
# ProjectManager.create(name=, domain=, description=,
|
# ProjectManager.create(name=, domain=, description=,
|
||||||
# enabled=, **kwargs)
|
# enabled=, **kwargs)
|
||||||
@ -110,6 +114,7 @@ class TestProjectCreate(TestProject):
|
|||||||
'is_domain',
|
'is_domain',
|
||||||
'name',
|
'name',
|
||||||
'parent_id',
|
'parent_id',
|
||||||
|
'tags'
|
||||||
)
|
)
|
||||||
self.assertEqual(collist, columns)
|
self.assertEqual(collist, columns)
|
||||||
datalist = (
|
datalist = (
|
||||||
@ -120,6 +125,7 @@ class TestProjectCreate(TestProject):
|
|||||||
False,
|
False,
|
||||||
self.project.name,
|
self.project.name,
|
||||||
self.project.parent_id,
|
self.project.parent_id,
|
||||||
|
self.project.tags
|
||||||
)
|
)
|
||||||
self.assertEqual(datalist, data)
|
self.assertEqual(datalist, data)
|
||||||
|
|
||||||
@ -134,6 +140,7 @@ class TestProjectCreate(TestProject):
|
|||||||
('disable', False),
|
('disable', False),
|
||||||
('name', self.project.name),
|
('name', self.project.name),
|
||||||
('parent', None),
|
('parent', None),
|
||||||
|
('tags', [])
|
||||||
]
|
]
|
||||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||||
|
|
||||||
@ -149,6 +156,7 @@ class TestProjectCreate(TestProject):
|
|||||||
'description': 'new desc',
|
'description': 'new desc',
|
||||||
'enabled': True,
|
'enabled': True,
|
||||||
'parent': None,
|
'parent': None,
|
||||||
|
'tags': []
|
||||||
}
|
}
|
||||||
# ProjectManager.create(name=, domain=, description=,
|
# ProjectManager.create(name=, domain=, description=,
|
||||||
# enabled=, **kwargs)
|
# enabled=, **kwargs)
|
||||||
@ -170,6 +178,7 @@ class TestProjectCreate(TestProject):
|
|||||||
('disable', False),
|
('disable', False),
|
||||||
('name', self.project.name),
|
('name', self.project.name),
|
||||||
('parent', None),
|
('parent', None),
|
||||||
|
('tags', [])
|
||||||
]
|
]
|
||||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||||
|
|
||||||
@ -185,6 +194,7 @@ class TestProjectCreate(TestProject):
|
|||||||
'description': None,
|
'description': None,
|
||||||
'enabled': True,
|
'enabled': True,
|
||||||
'parent': None,
|
'parent': None,
|
||||||
|
'tags': []
|
||||||
}
|
}
|
||||||
# ProjectManager.create(name=, domain=, description=,
|
# ProjectManager.create(name=, domain=, description=,
|
||||||
# enabled=, **kwargs)
|
# enabled=, **kwargs)
|
||||||
@ -206,6 +216,7 @@ class TestProjectCreate(TestProject):
|
|||||||
('disable', False),
|
('disable', False),
|
||||||
('name', self.project.name),
|
('name', self.project.name),
|
||||||
('parent', None),
|
('parent', None),
|
||||||
|
('tags', [])
|
||||||
]
|
]
|
||||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||||
mocker = mock.Mock()
|
mocker = mock.Mock()
|
||||||
@ -221,6 +232,7 @@ class TestProjectCreate(TestProject):
|
|||||||
'description': None,
|
'description': None,
|
||||||
'enabled': True,
|
'enabled': True,
|
||||||
'parent': None,
|
'parent': None,
|
||||||
|
'tags': []
|
||||||
}
|
}
|
||||||
self.projects_mock.create.assert_called_with(
|
self.projects_mock.create.assert_called_with(
|
||||||
**kwargs
|
**kwargs
|
||||||
@ -238,6 +250,7 @@ class TestProjectCreate(TestProject):
|
|||||||
('disable', False),
|
('disable', False),
|
||||||
('name', self.project.name),
|
('name', self.project.name),
|
||||||
('parent', None),
|
('parent', None),
|
||||||
|
('tags', [])
|
||||||
]
|
]
|
||||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||||
|
|
||||||
@ -253,6 +266,7 @@ class TestProjectCreate(TestProject):
|
|||||||
'description': None,
|
'description': None,
|
||||||
'enabled': True,
|
'enabled': True,
|
||||||
'parent': None,
|
'parent': None,
|
||||||
|
'tags': []
|
||||||
}
|
}
|
||||||
# ProjectManager.create(name=, domain=, description=,
|
# ProjectManager.create(name=, domain=, description=,
|
||||||
# enabled=, **kwargs)
|
# enabled=, **kwargs)
|
||||||
@ -288,6 +302,7 @@ class TestProjectCreate(TestProject):
|
|||||||
'description': None,
|
'description': None,
|
||||||
'enabled': False,
|
'enabled': False,
|
||||||
'parent': None,
|
'parent': None,
|
||||||
|
'tags': []
|
||||||
}
|
}
|
||||||
# ProjectManager.create(name=, domain=,
|
# ProjectManager.create(name=, domain=,
|
||||||
# description=, enabled=, **kwargs)
|
# description=, enabled=, **kwargs)
|
||||||
@ -324,6 +339,7 @@ class TestProjectCreate(TestProject):
|
|||||||
'parent': None,
|
'parent': None,
|
||||||
'fee': 'fi',
|
'fee': 'fi',
|
||||||
'fo': 'fum',
|
'fo': 'fum',
|
||||||
|
'tags': []
|
||||||
}
|
}
|
||||||
# ProjectManager.create(name=, domain=, description=,
|
# ProjectManager.create(name=, domain=, description=,
|
||||||
# enabled=, **kwargs)
|
# enabled=, **kwargs)
|
||||||
@ -352,6 +368,7 @@ class TestProjectCreate(TestProject):
|
|||||||
('enable', False),
|
('enable', False),
|
||||||
('disable', False),
|
('disable', False),
|
||||||
('name', self.project.name),
|
('name', self.project.name),
|
||||||
|
('tags', [])
|
||||||
]
|
]
|
||||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||||
|
|
||||||
@ -363,6 +380,7 @@ class TestProjectCreate(TestProject):
|
|||||||
'parent': self.parent.id,
|
'parent': self.parent.id,
|
||||||
'description': None,
|
'description': None,
|
||||||
'enabled': True,
|
'enabled': True,
|
||||||
|
'tags': []
|
||||||
}
|
}
|
||||||
|
|
||||||
self.projects_mock.create.assert_called_with(
|
self.projects_mock.create.assert_called_with(
|
||||||
@ -377,6 +395,7 @@ class TestProjectCreate(TestProject):
|
|||||||
'is_domain',
|
'is_domain',
|
||||||
'name',
|
'name',
|
||||||
'parent_id',
|
'parent_id',
|
||||||
|
'tags'
|
||||||
)
|
)
|
||||||
self.assertEqual(columns, collist)
|
self.assertEqual(columns, collist)
|
||||||
datalist = (
|
datalist = (
|
||||||
@ -387,6 +406,7 @@ class TestProjectCreate(TestProject):
|
|||||||
self.project.is_domain,
|
self.project.is_domain,
|
||||||
self.project.name,
|
self.project.name,
|
||||||
self.parent.id,
|
self.parent.id,
|
||||||
|
self.project.tags
|
||||||
)
|
)
|
||||||
self.assertEqual(data, datalist)
|
self.assertEqual(data, datalist)
|
||||||
|
|
||||||
@ -417,6 +437,43 @@ class TestProjectCreate(TestProject):
|
|||||||
parsed_args,
|
parsed_args,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_project_create_with_tags(self):
|
||||||
|
arglist = [
|
||||||
|
'--domain', self.project.domain_id,
|
||||||
|
'--tag', 'foo',
|
||||||
|
self.project.name,
|
||||||
|
]
|
||||||
|
verifylist = [
|
||||||
|
('domain', self.project.domain_id),
|
||||||
|
('enable', False),
|
||||||
|
('disable', False),
|
||||||
|
('name', self.project.name),
|
||||||
|
('parent', None),
|
||||||
|
('tags', ['foo'])
|
||||||
|
]
|
||||||
|
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||||
|
|
||||||
|
# In base command class ShowOne in cliff, abstract method take_action()
|
||||||
|
# returns a two-part tuple with a tuple of column names and a tuple of
|
||||||
|
# data to be shown.
|
||||||
|
columns, data = self.cmd.take_action(parsed_args)
|
||||||
|
|
||||||
|
# Set expected values
|
||||||
|
kwargs = {
|
||||||
|
'name': self.project.name,
|
||||||
|
'domain': self.project.domain_id,
|
||||||
|
'description': None,
|
||||||
|
'enabled': True,
|
||||||
|
'parent': None,
|
||||||
|
'tags': ['foo']
|
||||||
|
}
|
||||||
|
self.projects_mock.create.assert_called_with(
|
||||||
|
**kwargs
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(self.columns, columns)
|
||||||
|
self.assertEqual(self.datalist, data)
|
||||||
|
|
||||||
|
|
||||||
class TestProjectDelete(TestProject):
|
class TestProjectDelete(TestProject):
|
||||||
|
|
||||||
@ -816,6 +873,38 @@ class TestProjectSet(TestProject):
|
|||||||
)
|
)
|
||||||
self.assertIsNone(result)
|
self.assertIsNone(result)
|
||||||
|
|
||||||
|
def test_project_set_tags(self):
|
||||||
|
arglist = [
|
||||||
|
'--name', 'qwerty',
|
||||||
|
'--domain', self.project.domain_id,
|
||||||
|
'--tag', 'foo',
|
||||||
|
self.project.name,
|
||||||
|
]
|
||||||
|
verifylist = [
|
||||||
|
('name', 'qwerty'),
|
||||||
|
('domain', self.project.domain_id),
|
||||||
|
('enable', False),
|
||||||
|
('disable', False),
|
||||||
|
('project', self.project.name),
|
||||||
|
('tags', ['foo'])
|
||||||
|
]
|
||||||
|
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||||
|
|
||||||
|
result = self.cmd.take_action(parsed_args)
|
||||||
|
|
||||||
|
# Set expected values
|
||||||
|
kwargs = {
|
||||||
|
'name': 'qwerty',
|
||||||
|
'tags': ['foo']
|
||||||
|
}
|
||||||
|
# ProjectManager.update(project, name=, domain=, description=,
|
||||||
|
# enabled=, **kwargs)
|
||||||
|
self.projects_mock.update.assert_called_with(
|
||||||
|
self.project.id,
|
||||||
|
**kwargs
|
||||||
|
)
|
||||||
|
self.assertIsNone(result)
|
||||||
|
|
||||||
|
|
||||||
class TestProjectShow(TestProject):
|
class TestProjectShow(TestProject):
|
||||||
|
|
||||||
@ -867,6 +956,7 @@ class TestProjectShow(TestProject):
|
|||||||
'is_domain',
|
'is_domain',
|
||||||
'name',
|
'name',
|
||||||
'parent_id',
|
'parent_id',
|
||||||
|
'tags'
|
||||||
)
|
)
|
||||||
self.assertEqual(collist, columns)
|
self.assertEqual(collist, columns)
|
||||||
datalist = (
|
datalist = (
|
||||||
@ -877,6 +967,7 @@ class TestProjectShow(TestProject):
|
|||||||
False,
|
False,
|
||||||
self.project.name,
|
self.project.name,
|
||||||
self.project.parent_id,
|
self.project.parent_id,
|
||||||
|
self.project.tags
|
||||||
)
|
)
|
||||||
self.assertEqual(datalist, data)
|
self.assertEqual(datalist, data)
|
||||||
|
|
||||||
@ -926,6 +1017,7 @@ class TestProjectShow(TestProject):
|
|||||||
'name',
|
'name',
|
||||||
'parent_id',
|
'parent_id',
|
||||||
'parents',
|
'parents',
|
||||||
|
'tags'
|
||||||
)
|
)
|
||||||
self.assertEqual(columns, collist)
|
self.assertEqual(columns, collist)
|
||||||
datalist = (
|
datalist = (
|
||||||
@ -936,7 +1028,8 @@ class TestProjectShow(TestProject):
|
|||||||
self.project.is_domain,
|
self.project.is_domain,
|
||||||
self.project.name,
|
self.project.name,
|
||||||
self.project.parent_id,
|
self.project.parent_id,
|
||||||
[{'project': {'id': self.project.parent_id}}]
|
[{'project': {'id': self.project.parent_id}}],
|
||||||
|
self.project.tags
|
||||||
)
|
)
|
||||||
self.assertEqual(data, datalist)
|
self.assertEqual(data, datalist)
|
||||||
|
|
||||||
@ -985,6 +1078,7 @@ class TestProjectShow(TestProject):
|
|||||||
'name',
|
'name',
|
||||||
'parent_id',
|
'parent_id',
|
||||||
'subtree',
|
'subtree',
|
||||||
|
'tags'
|
||||||
)
|
)
|
||||||
self.assertEqual(columns, collist)
|
self.assertEqual(columns, collist)
|
||||||
datalist = (
|
datalist = (
|
||||||
@ -995,7 +1089,8 @@ class TestProjectShow(TestProject):
|
|||||||
self.project.is_domain,
|
self.project.is_domain,
|
||||||
self.project.name,
|
self.project.name,
|
||||||
self.project.parent_id,
|
self.project.parent_id,
|
||||||
[{'project': {'id': 'children-id'}}]
|
[{'project': {'id': 'children-id'}}],
|
||||||
|
self.project.tags
|
||||||
)
|
)
|
||||||
self.assertEqual(data, datalist)
|
self.assertEqual(data, datalist)
|
||||||
|
|
||||||
@ -1047,6 +1142,7 @@ class TestProjectShow(TestProject):
|
|||||||
'parent_id',
|
'parent_id',
|
||||||
'parents',
|
'parents',
|
||||||
'subtree',
|
'subtree',
|
||||||
|
'tags'
|
||||||
)
|
)
|
||||||
self.assertEqual(columns, collist)
|
self.assertEqual(columns, collist)
|
||||||
datalist = (
|
datalist = (
|
||||||
@ -1058,7 +1154,8 @@ class TestProjectShow(TestProject):
|
|||||||
self.project.name,
|
self.project.name,
|
||||||
self.project.parent_id,
|
self.project.parent_id,
|
||||||
[{'project': {'id': self.project.parent_id}}],
|
[{'project': {'id': self.project.parent_id}}],
|
||||||
[{'project': {'id': 'children-id'}}]
|
[{'project': {'id': 'children-id'}}],
|
||||||
|
self.project.tags
|
||||||
)
|
)
|
||||||
self.assertEqual(data, datalist)
|
self.assertEqual(data, datalist)
|
||||||
|
|
||||||
|
8
releasenotes/notes/bp-project-tags-b544aef9672d415b.yaml
Normal file
8
releasenotes/notes/bp-project-tags-b544aef9672d415b.yaml
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
---
|
||||||
|
features:
|
||||||
|
- |
|
||||||
|
Add ``--tag`` option to ``project create`` command, ``--tag``, ``--clear-tags``, and
|
||||||
|
``--remove-tag`` options to ``project set`` command. Add ``--tags``, ``--tags-any``,
|
||||||
|
``--not-tags``, and ``--not-tags-any`` options to ``project list`` command to filter
|
||||||
|
list results by different projects based on their tags.
|
||||||
|
[`blueprint project-tags <https://blueprints.launchpad.net/keystone/+spec/project-tags>`_]
|
Loading…
x
Reference in New Issue
Block a user