Hello community,

here is the log from the commit of package python-keystoneclient for 
openSUSE:Factory checked in at 2018-02-14 10:50:33
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-keystoneclient (Old)
 and      /work/SRC/openSUSE:Factory/.python-keystoneclient.new (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-keystoneclient"

Wed Feb 14 10:50:33 2018 rev:27 rq:575938 version:3.15.0

Changes:
--------
--- 
/work/SRC/openSUSE:Factory/python-keystoneclient/python-keystoneclient.changes  
    2018-01-24 15:26:57.372319298 +0100
+++ 
/work/SRC/openSUSE:Factory/.python-keystoneclient.new/python-keystoneclient.changes
 2018-02-14 10:50:35.499908695 +0100
@@ -1,0 +2,10 @@
+Mon Feb 12 09:58:03 UTC 2018 - cloud-de...@suse.de
+
+- update to version 3.15.0 (bsc#1078607)
+  - Create doc/requirements.txt
+  - Add system role functionality
+  - Add CRUD support for application credentials
+  - Updated from global requirements
+  - Add project tags to keystoneclient
+
+-------------------------------------------------------------------

Old:
----
  python-keystoneclient-3.14.0.tar.gz

New:
----
  python-keystoneclient-3.15.0.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-keystoneclient.spec ++++++
--- /var/tmp/diff_new_pack.HhYWXc/_old  2018-02-14 10:50:36.355877891 +0100
+++ /var/tmp/diff_new_pack.HhYWXc/_new  2018-02-14 10:50:36.363877603 +0100
@@ -16,29 +16,27 @@
 #
 
 
-%global sname python-keystoneclient
 Name:           python-keystoneclient
-Version:        3.14.0
+Version:        3.15.0
 Release:        0
 Summary:        Client library for OpenStack Identity API
 License:        Apache-2.0
 Group:          Development/Languages/Python
-Url:            http://launchpad.net/%{sname}
-Source0:        
https://files.pythonhosted.org/packages/source/p/%{sname}/%{sname}-%{version}.tar.gz
+Url:            https://launchpad.net/python-keystoneclient
+Source0:        
https://files.pythonhosted.org/packages/source/p/python-keystoneclient/python-keystoneclient-3.15.0.tar.gz
 BuildRequires:  openssl
 BuildRequires:  openstack-macros
 BuildRequires:  python-devel
 BuildRequires:  python2-debtcollector >= 1.2.0
-BuildRequires:  python2-keystoneauth1 >= 3.2.0
+BuildRequires:  python2-keystoneauth1 >= 3.3.0
 BuildRequires:  python2-lxml >= 3.4.1
 BuildRequires:  python2-mock >= 2.0.0
-BuildRequires:  python2-oslo.config >= 4.6.0
+BuildRequires:  python2-oslo.config >= 5.1.0
 BuildRequires:  python2-oslo.i18n >= 3.15.3
 BuildRequires:  python2-oslo.serialization >= 2.18.0
-BuildRequires:  python2-oslo.utils >= 3.31.0
-BuildRequires:  python2-oslotest >= 1.10.0
+BuildRequires:  python2-oslo.utils >= 3.33.0
+BuildRequires:  python2-oslotest >= 3.2.0
 BuildRequires:  python2-pbr >= 2.0.0
-BuildRequires:  python2-positional
 BuildRequires:  python2-requests-mock >= 1.1.0
 BuildRequires:  python2-six >= 1.10.0
 BuildRequires:  python2-testrepository >= 0.0.18
@@ -46,14 +44,14 @@
 BuildRequires:  python2-testscenarios >= 0.4
 BuildRequires:  python3-debtcollector >= 1.2.0
 BuildRequires:  python3-devel
-BuildRequires:  python3-keystoneauth1 >= 3.2.0
+BuildRequires:  python3-keystoneauth1 >= 3.3.0
 BuildRequires:  python3-lxml >= 3.4.1
 BuildRequires:  python3-mock >= 2.0.0
-BuildRequires:  python3-oslo.config >= 4.6.0
+BuildRequires:  python3-oslo.config >= 5.1.0
 BuildRequires:  python3-oslo.i18n >= 3.15.3
 BuildRequires:  python3-oslo.serialization >= 2.18.0
-BuildRequires:  python3-oslo.utils >= 3.31.0
-BuildRequires:  python3-oslotest >= 1.10.0
+BuildRequires:  python3-oslo.utils >= 3.33.0
+BuildRequires:  python3-oslotest >= 3.2.0
 BuildRequires:  python3-pbr >= 2.0.0
 BuildRequires:  python3-requests-mock >= 1.1.0
 BuildRequires:  python3-six >= 1.10.0
@@ -61,12 +59,11 @@
 BuildRequires:  python3-testresources >= 2.0.0
 BuildRequires:  python3-testscenarios >= 0.4
 Requires:       python-debtcollector >= 1.2.0
-Requires:       python-keystoneauth1 >= 3.2.0
-Requires:       python-oslo.config >= 4.6.0
+Requires:       python-keystoneauth1 >= 3.3.0
+Requires:       python-oslo.config >= 5.1.0
 Requires:       python-oslo.i18n >= 3.15.3
 Requires:       python-oslo.serialization >= 2.18.0
-Requires:       python-oslo.utils >= 3.31.0
-Requires:       python-positional
+Requires:       python-oslo.utils >= 3.33.0
 Requires:       python-requests >= 2.14.2
 Requires:       python-six >= 1.10.0
 Requires:       python-stevedore >= 1.20.0
@@ -87,7 +84,7 @@
 Identity API.
 
 %prep
-%autosetup -p1 -n %{sname}-%{version}
+%autosetup -p1 -n python-keystoneclient-3.15.0
 %py_req_cleanup
 # disable intersphinx - no network access during build
 echo "intersphinx_mapping = {}" >> doc/source/conf.py

++++++ python-keystoneclient-3.14.0.tar.gz -> 
python-keystoneclient-3.15.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python-keystoneclient-3.14.0/AUTHORS 
new/python-keystoneclient-3.15.0/AUTHORS
--- old/python-keystoneclient-3.14.0/AUTHORS    2017-12-09 05:06:22.000000000 
+0100
+++ new/python-keystoneclient-3.15.0/AUTHORS    2018-01-24 21:10:42.000000000 
+0100
@@ -46,6 +46,7 @@
 Clark Boylan <clark.boy...@gmail.com>
 Claudiu Belu <cb...@cloudbasesolutions.com>
 Clint Byrum <cl...@fewbar.com>
+Colleen Murphy <coll...@gazlene.net>
 Corey Bryant <corey.bry...@canonical.com>
 Cyril Roelandt <cyril.roela...@enovance.com>
 Dan Prince <dpri...@redhat.com>
@@ -164,6 +165,7 @@
 Peter Portante <peter.porta...@redhat.com>
 Pradeep Kilambi <pkila...@cisco.com>
 Qin Zhao <chaoc...@gmail.com>
+QinglinCheng <qinglin.ch...@easystack.cn>
 Qiu Yu <qi...@ebaysf.com>
 Rakesh H S <r...@hp.com>
 Rob Crittenden <rcrit...@redhat.com>
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python-keystoneclient-3.14.0/ChangeLog 
new/python-keystoneclient-3.15.0/ChangeLog
--- old/python-keystoneclient-3.14.0/ChangeLog  2017-12-09 05:06:22.000000000 
+0100
+++ new/python-keystoneclient-3.15.0/ChangeLog  2018-01-24 21:10:42.000000000 
+0100
@@ -1,6 +1,16 @@
 CHANGES
 =======
 
+3.15.0
+------
+
+* Add system role functionality
+* Add CRUD support for application credentials
+* Updated from global requirements
+* Add project tags to keystoneclient
+* Create doc/requirements.txt
+* Updated from global requirements
+
 3.14.0
 ------
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python-keystoneclient-3.14.0/PKG-INFO 
new/python-keystoneclient-3.15.0/PKG-INFO
--- old/python-keystoneclient-3.14.0/PKG-INFO   2017-12-09 05:06:23.000000000 
+0100
+++ new/python-keystoneclient-3.15.0/PKG-INFO   2018-01-24 21:10:43.000000000 
+0100
@@ -1,6 +1,6 @@
 Metadata-Version: 1.1
 Name: python-keystoneclient
-Version: 3.14.0
+Version: 3.15.0
 Summary: Client Library for OpenStack Identity
 Home-page: https://docs.openstack.org/python-keystoneclient/latest/
 Author: OpenStack
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python-keystoneclient-3.14.0/doc/requirements.txt 
new/python-keystoneclient-3.15.0/doc/requirements.txt
--- old/python-keystoneclient-3.14.0/doc/requirements.txt       1970-01-01 
01:00:00.000000000 +0100
+++ new/python-keystoneclient-3.15.0/doc/requirements.txt       2018-01-24 
21:08:40.000000000 +0100
@@ -0,0 +1,10 @@
+# The order of packages is significant, because pip processes them in the order
+# of appearance. Changing the order has an impact on the overall integration
+# process, which may cause wedges in the gate later.
+
+# These are needed for docs generation
+openstackdocstheme>=1.17.0 # Apache-2.0
+sphinx!=1.6.6,>=1.6.2 # BSD
+reno>=2.5.0 # Apache-2.0
+lxml!=3.7.0,>=3.4.1 # BSD
+fixtures>=3.0.0 # Apache-2.0/BSD
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python-keystoneclient-3.14.0/keystoneclient/base.py 
new/python-keystoneclient-3.15.0/keystoneclient/base.py
--- old/python-keystoneclient-3.14.0/keystoneclient/base.py     2017-12-09 
05:02:58.000000000 +0100
+++ new/python-keystoneclient-3.15.0/keystoneclient/base.py     2018-01-24 
21:08:40.000000000 +0100
@@ -356,6 +356,13 @@
         if params is None:
             return ''
         else:
+            # NOTE(spilla) Since the manager cannot take in a hyphen as a
+            # key in the kwarg, it is passed in with a _.  This needs to be
+            # replaced with a proper hyphen for the URL to work properly.
+            tags_params = ('tags_any', 'not_tags', 'not_tags_any')
+            for tag_param in tags_params:
+                if tag_param in params:
+                    params[tag_param.replace('_', '-')] = params.pop(tag_param)
             return '?%s' % urllib.parse.urlencode(params, doseq=True)
 
     def build_key_only_query(self, params_list):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/python-keystoneclient-3.14.0/keystoneclient/tests/functional/v3/client_fixtures.py
 
new/python-keystoneclient-3.15.0/keystoneclient/tests/functional/v3/client_fixtures.py
--- 
old/python-keystoneclient-3.14.0/keystoneclient/tests/functional/v3/client_fixtures.py
      2017-12-09 05:02:58.000000000 +0100
+++ 
new/python-keystoneclient-3.15.0/keystoneclient/tests/functional/v3/client_fixtures.py
      2018-01-24 21:08:40.000000000 +0100
@@ -71,9 +71,10 @@
 
 class Project(Base):
 
-    def __init__(self, client, domain_id=None, parent=None):
+    def __init__(self, client, domain_id=None, parent=None, tags=None):
         super(Project, self).__init__(client, domain_id)
         self.parent = parent
+        self.tags = tags if tags else []
 
     def setUp(self):
         super(Project, self).setUp()
@@ -81,7 +82,8 @@
         self.ref = {'name': RESOURCE_NAME_PREFIX + uuid.uuid4().hex,
                     'domain': self.domain_id,
                     'enabled': True,
-                    'parent': self.parent}
+                    'parent': self.parent,
+                    'tags': self.tags}
         self.entity = self.client.projects.create(**self.ref)
         self.addCleanup(self.client.projects.delete, self.entity)
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/python-keystoneclient-3.14.0/keystoneclient/tests/functional/v3/test_projects.py
 
new/python-keystoneclient-3.15.0/keystoneclient/tests/functional/v3/test_projects.py
--- 
old/python-keystoneclient-3.14.0/keystoneclient/tests/functional/v3/test_projects.py
        2017-12-09 05:02:58.000000000 +0100
+++ 
new/python-keystoneclient-3.15.0/keystoneclient/tests/functional/v3/test_projects.py
        2018-01-24 21:08:40.000000000 +0100
@@ -53,6 +53,7 @@
 
         self.test_project = fixtures.Project(self.client, self.test_domain.id)
         self.useFixture(self.test_project)
+        self.special_tag = '~`!@#$%^&*()-_+=<>.? \'"'
 
     def test_create_subproject(self):
         project_ref = {
@@ -188,3 +189,257 @@
         self.assertRaises(http.NotFound,
                           self.client.projects.get,
                           project.id)
+
+    def test_list_projects_with_tag_filters(self):
+        project_one = fixtures.Project(
+            self.client, self.test_domain.id,
+            tags=['tag1'])
+        project_two = fixtures.Project(
+            self.client, self.test_domain.id,
+            tags=['tag1', 'tag2'])
+        project_three = fixtures.Project(
+            self.client, self.test_domain.id,
+            tags=['tag2', 'tag3'])
+
+        self.useFixture(project_one)
+        self.useFixture(project_two)
+        self.useFixture(project_three)
+
+        projects = self.client.projects.list(tags='tag1')
+        project_ids = []
+        for project in projects:
+            project_ids.append(project.id)
+        self.assertIn(project_one.id, project_ids)
+
+        projects = self.client.projects.list(tags_any='tag1')
+        project_ids = []
+        for project in projects:
+            project_ids.append(project.id)
+        self.assertIn(project_one.id, project_ids)
+        self.assertIn(project_two.id, project_ids)
+
+        projects = self.client.projects.list(not_tags='tag1')
+        project_ids = []
+        for project in projects:
+            project_ids.append(project.id)
+        self.assertNotIn(project_one.id, project_ids)
+
+        projects = self.client.projects.list(not_tags_any='tag1,tag2')
+        project_ids = []
+        for project in projects:
+            project_ids.append(project.id)
+        self.assertNotIn(project_one.id, project_ids)
+        self.assertNotIn(project_two.id, project_ids)
+        self.assertNotIn(project_three.id, project_ids)
+
+        projects = self.client.projects.list(tags='tag1,tag2')
+        project_ids = []
+        for project in projects:
+            project_ids.append(project.id)
+        self.assertNotIn(project_one.id, project_ids)
+        self.assertIn(project_two.id, project_ids)
+        self.assertNotIn(project_three.id, project_ids)
+
+    def test_add_tag(self):
+        project = fixtures.Project(self.client, self.test_domain.id)
+        self.useFixture(project)
+
+        tags = self.client.projects.get(project.id).tags
+        self.assertEqual([], tags)
+
+        project.add_tag('tag1')
+        tags = self.client.projects.get(project.id).tags
+        self.assertEqual(['tag1'], tags)
+
+        # verify there is an error when you try to add the same tag
+        self.assertRaises(http.BadRequest,
+                          project.add_tag,
+                          'tag1')
+
+    def test_update_tags(self):
+        project = fixtures.Project(self.client, self.test_domain.id)
+        self.useFixture(project)
+
+        tags = self.client.projects.get(project.id).tags
+        self.assertEqual([], tags)
+
+        project.update_tags(['tag1', 'tag2', self.special_tag])
+        tags = self.client.projects.get(project.id).tags
+        self.assertIn('tag1', tags)
+        self.assertIn('tag2', tags)
+        self.assertIn(self.special_tag, tags)
+        self.assertEqual(3, len(tags))
+
+        project.update_tags([])
+        tags = self.client.projects.get(project.id).tags
+        self.assertEqual([], tags)
+
+        # cannot have duplicate tags in update
+        self.assertRaises(http.BadRequest,
+                          project.update_tags,
+                          ['tag1', 'tag1'])
+
+    def test_delete_tag(self):
+        project = fixtures.Project(
+            self.client, self.test_domain.id,
+            tags=['tag1', self.special_tag])
+        self.useFixture(project)
+
+        project.delete_tag('tag1')
+        tags = self.client.projects.get(project.id).tags
+        self.assertEqual([self.special_tag], tags)
+
+        project.delete_tag(self.special_tag)
+        tags = self.client.projects.get(project.id).tags
+        self.assertEqual([], tags)
+
+    def test_delete_all_tags(self):
+        project_one = fixtures.Project(
+            self.client, self.test_domain.id,
+            tags=['tag1'])
+
+        project_two = fixtures.Project(
+            self.client, self.test_domain.id,
+            tags=['tag1', 'tag2', self.special_tag])
+
+        project_three = fixtures.Project(
+            self.client, self.test_domain.id,
+            tags=[])
+
+        self.useFixture(project_one)
+        self.useFixture(project_two)
+        self.useFixture(project_three)
+
+        result_one = project_one.delete_all_tags()
+        tags_one = self.client.projects.get(project_one.id).tags
+        tags_two = self.client.projects.get(project_two.id).tags
+        self.assertEqual([], result_one)
+        self.assertEqual([], tags_one)
+        self.assertIn('tag1', tags_two)
+
+        result_two = project_two.delete_all_tags()
+        tags_two = self.client.projects.get(project_two.id).tags
+        self.assertEqual([], result_two)
+        self.assertEqual([], tags_two)
+
+        result_three = project_three.delete_all_tags()
+        tags_three = self.client.projects.get(project_three.id).tags
+        self.assertEqual([], result_three)
+        self.assertEqual([], tags_three)
+
+    def test_list_tags(self):
+        tags_one = ['tag1']
+        project_one = fixtures.Project(
+            self.client, self.test_domain.id,
+            tags=tags_one)
+
+        tags_two = ['tag1', 'tag2']
+        project_two = fixtures.Project(
+            self.client, self.test_domain.id,
+            tags=tags_two)
+
+        tags_three = []
+        project_three = fixtures.Project(
+            self.client, self.test_domain.id,
+            tags=tags_three)
+
+        self.useFixture(project_one)
+        self.useFixture(project_two)
+        self.useFixture(project_three)
+
+        result_one = project_one.list_tags()
+        result_two = project_two.list_tags()
+        result_three = project_three.list_tags()
+
+        for tag in tags_one:
+            self.assertIn(tag, result_one)
+        self.assertEqual(1, len(result_one))
+
+        for tag in tags_two:
+            self.assertIn(tag, result_two)
+        self.assertEqual(2, len(result_two))
+
+        for tag in tags_three:
+            self.assertIn(tag, result_three)
+        self.assertEqual(0, len(result_three))
+
+    def test_check_tag(self):
+        project = fixtures.Project(
+            self.client, self.test_domain.id,
+            tags=['tag1'])
+        self.useFixture(project)
+
+        tags = self.client.projects.get(project.id).tags
+        self.assertEqual(['tag1'], tags)
+        self.assertTrue(project.check_tag('tag1'))
+        self.assertFalse(project.check_tag('tag2'))
+        self.assertFalse(project.check_tag(self.special_tag))
+
+    def test_add_invalid_tags(self):
+        project_one = fixtures.Project(
+            self.client, self.test_domain.id)
+
+        self.useFixture(project_one)
+
+        self.assertRaises(exceptions.BadRequest,
+                          project_one.add_tag,
+                          ',')
+        self.assertRaises(exceptions.BadRequest,
+                          project_one.add_tag,
+                          '/')
+        self.assertRaises(exceptions.BadRequest,
+                          project_one.add_tag,
+                          '')
+
+    def test_update_invalid_tags(self):
+        tags_comma = ['tag1', ',']
+        tags_slash = ['tag1', '/']
+        tags_blank = ['tag1', '']
+        project_one = fixtures.Project(
+            self.client, self.test_domain.id)
+
+        self.useFixture(project_one)
+
+        self.assertRaises(exceptions.BadRequest,
+                          project_one.update_tags,
+                          tags_comma)
+        self.assertRaises(exceptions.BadRequest,
+                          project_one.update_tags,
+                          tags_slash)
+        self.assertRaises(exceptions.BadRequest,
+                          project_one.update_tags,
+                          tags_blank)
+
+    def test_create_project_invalid_tags(self):
+        project_ref = {
+            'name': fixtures.RESOURCE_NAME_PREFIX + uuid.uuid4().hex,
+            'domain': self.test_domain.id,
+            'enabled': True,
+            'description': uuid.uuid4().hex,
+            'tags': ','}
+
+        self.assertRaises(exceptions.BadRequest,
+                          self.client.projects.create,
+                          **project_ref)
+
+        project_ref = {
+            'name': fixtures.RESOURCE_NAME_PREFIX + uuid.uuid4().hex,
+            'domain': self.test_domain.id,
+            'enabled': True,
+            'description': uuid.uuid4().hex,
+            'tags': '/'}
+
+        self.assertRaises(exceptions.BadRequest,
+                          self.client.projects.create,
+                          **project_ref)
+
+        project_ref = {
+            'name': fixtures.RESOURCE_NAME_PREFIX + uuid.uuid4().hex,
+            'domain': self.test_domain.id,
+            'enabled': True,
+            'description': uuid.uuid4().hex,
+            'tags': ''}
+
+        self.assertRaises(exceptions.BadRequest,
+                          self.client.projects.create,
+                          **project_ref)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/python-keystoneclient-3.14.0/keystoneclient/tests/unit/v3/test_application_credentials.py
 
new/python-keystoneclient-3.15.0/keystoneclient/tests/unit/v3/test_application_credentials.py
--- 
old/python-keystoneclient-3.14.0/keystoneclient/tests/unit/v3/test_application_credentials.py
       1970-01-01 01:00:00.000000000 +0100
+++ 
new/python-keystoneclient-3.15.0/keystoneclient/tests/unit/v3/test_application_credentials.py
       2018-01-24 21:09:04.000000000 +0100
@@ -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 uuid
+
+from oslo_utils import timeutils
+
+from keystoneclient import exceptions
+from keystoneclient.tests.unit.v3 import utils
+from keystoneclient.v3 import application_credentials
+
+
+class ApplicationCredentialTests(utils.ClientTestCase, utils.CrudTests):
+    def setUp(self):
+        super(ApplicationCredentialTests, self).setUp()
+        self.key = 'application_credential'
+        self.collection_key = 'application_credentials'
+        self.model = application_credentials.ApplicationCredential
+        self.manager = self.client.application_credentials
+        self.path_prefix = 'users/%s' % self.TEST_USER_ID
+
+    def new_ref(self, **kwargs):
+        kwargs = super(ApplicationCredentialTests, self).new_ref(**kwargs)
+        kwargs.setdefault('name', uuid.uuid4().hex)
+        kwargs.setdefault('description', uuid.uuid4().hex)
+        kwargs.setdefault('unrestricted', False)
+        return kwargs
+
+    def test_create_with_roles(self):
+        ref = self.new_ref(user=uuid.uuid4().hex)
+        ref['roles'] = [{'name': 'atestrole'}]
+        req_ref = ref.copy()
+        req_ref.pop('id')
+        user = req_ref.pop('user')
+
+        self.stub_entity('POST',
+                         ['users', user, self.collection_key],
+                         status_code=201, entity=req_ref)
+
+        super(ApplicationCredentialTests, self).test_create(ref=ref,
+                                                            req_ref=req_ref)
+
+    def test_create_with_role_id_and_names(self):
+        ref = self.new_ref(user=uuid.uuid4().hex)
+        ref['roles'] = [{'name': 'atestrole', 'domain': 'nondefault'},
+                        uuid.uuid4().hex]
+        req_ref = ref.copy()
+        req_ref.pop('id')
+        user = req_ref.pop('user')
+
+        req_ref['roles'] = [{'name': 'atestrole', 'domain': 'nondefault'},
+                            {'id': ref['roles'][1]}]
+        self.stub_entity('POST',
+                         ['users', user, self.collection_key],
+                         status_code=201, entity=req_ref)
+
+        super(ApplicationCredentialTests, self).test_create(ref=ref,
+                                                            req_ref=req_ref)
+
+    def test_create_expires(self):
+        ref = self.new_ref(user=uuid.uuid4().hex)
+        ref['expires_at'] = timeutils.parse_isotime(
+            '2013-03-04T12:00:01.000000Z')
+        req_ref = ref.copy()
+        req_ref.pop('id')
+        user = req_ref.pop('user')
+
+        req_ref['expires_at'] = '2013-03-04T12:00:01.000000Z'
+
+        self.stub_entity('POST',
+                         ['users', user, self.collection_key],
+                         status_code=201, entity=req_ref)
+
+        super(ApplicationCredentialTests, self).test_create(ref=ref,
+                                                            req_ref=req_ref)
+
+    def test_create_unrestricted(self):
+        ref = self.new_ref(user=uuid.uuid4().hex)
+        ref['unrestricted'] = True
+        req_ref = ref.copy()
+        req_ref.pop('id')
+        user = req_ref.pop('user')
+
+        self.stub_entity('POST',
+                         ['users', user, self.collection_key],
+                         status_code=201, entity=req_ref)
+
+        super(ApplicationCredentialTests, self).test_create(ref=ref,
+                                                            req_ref=req_ref)
+
+    def test_get(self):
+        ref = self.new_ref(user=uuid.uuid4().hex)
+
+        self.stub_entity(
+            'GET', ['users', ref['user'], self.collection_key, ref['id']],
+            entity=ref)
+        returned = self.manager.get(ref['id'], ref['user'])
+        self.assertIsInstance(returned, self.model)
+        for attr in ref:
+            self.assertEqual(
+                getattr(returned, attr),
+                ref[attr],
+                'Expected different %s' % attr)
+
+    def test_update(self):
+        self.assertRaises(exceptions.MethodNotImplemented, self.manager.update)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/python-keystoneclient-3.14.0/keystoneclient/tests/unit/v3/test_projects.py 
new/python-keystoneclient-3.15.0/keystoneclient/tests/unit/v3/test_projects.py
--- 
old/python-keystoneclient-3.14.0/keystoneclient/tests/unit/v3/test_projects.py  
    2017-12-09 05:02:58.000000000 +0100
+++ 
new/python-keystoneclient-3.15.0/keystoneclient/tests/unit/v3/test_projects.py  
    2018-01-24 21:08:40.000000000 +0100
@@ -312,3 +312,86 @@
         # server, a different implementation might not fail this request.
         self.assertRaises(ksa_exceptions.Forbidden, self.manager.update,
                           ref['id'], **utils.parameterize(req_ref))
+
+    def test_add_tag(self):
+        ref = self.new_ref()
+        tag_name = "blue"
+
+        self.stub_url("PUT",
+                      parts=[self.collection_key, ref['id'], "tags", tag_name],
+                      status_code=201)
+        self.manager.add_tag(ref['id'], tag_name)
+
+    def test_update_tags(self):
+        new_tags = ["blue", "orange"]
+        ref = self.new_ref()
+
+        self.stub_url("PUT",
+                      parts=[self.collection_key, ref['id'], "tags"],
+                      json={"tags": new_tags},
+                      status_code=200)
+
+        ret = self.manager.update_tags(ref['id'], new_tags)
+        self.assertEqual(ret, new_tags)
+
+    def test_delete_tag(self):
+        ref = self.new_ref()
+        tag_name = "blue"
+
+        self.stub_url("DELETE",
+                      parts=[self.collection_key, ref['id'], "tags", tag_name],
+                      status_code=204)
+
+        self.manager.delete_tag(ref['id'], tag_name)
+
+    def test_delete_all_tags(self):
+        ref = self.new_ref()
+
+        self.stub_url("PUT",
+                      parts=[self.collection_key, ref['id'], "tags"],
+                      json={"tags": []},
+                      status_code=200)
+
+        ret = self.manager.update_tags(ref['id'], [])
+        self.assertEqual([], ret)
+
+    def test_list_tags(self):
+        ref = self.new_ref()
+        tags = ["blue", "orange", "green"]
+
+        self.stub_url("GET",
+                      parts=[self.collection_key, ref['id'], "tags"],
+                      json={"tags": tags},
+                      status_code=200)
+
+        ret_tags = self.manager.list_tags(ref['id'])
+        self.assertEqual(tags, ret_tags)
+
+    def test_check_tag(self):
+        ref = self.new_ref()
+
+        tag_name = "blue"
+        self.stub_url("HEAD",
+                      parts=[self.collection_key, ref['id'], "tags", tag_name],
+                      status_code=204)
+        self.assertTrue(self.manager.check_tag(ref['id'], tag_name))
+
+        no_tag = "orange"
+        self.stub_url("HEAD",
+                      parts=[self.collection_key, ref['id'], "tags", no_tag],
+                      status_code=404)
+        self.assertFalse(self.manager.check_tag(ref['id'], no_tag))
+
+    def _build_project_response(self, tags):
+        project_id = uuid.uuid4().hex
+        ret = {"projects": [
+            {"is_domain": False,
+             "description": "",
+             "tags": tags,
+             "enabled": True,
+             "id": project_id,
+             "parent_id": "default",
+             "domain_id": "default",
+             "name": project_id}
+        ]}
+        return ret
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/python-keystoneclient-3.14.0/keystoneclient/tests/unit/v3/test_role_assignments.py
 
new/python-keystoneclient-3.15.0/keystoneclient/tests/unit/v3/test_role_assignments.py
--- 
old/python-keystoneclient-3.14.0/keystoneclient/tests/unit/v3/test_role_assignments.py
      2017-12-09 05:02:58.000000000 +0100
+++ 
new/python-keystoneclient-3.15.0/keystoneclient/tests/unit/v3/test_role_assignments.py
      2018-01-24 21:09:04.000000000 +0100
@@ -23,6 +23,32 @@
         self.collection_key = 'role_assignments'
         self.model = role_assignments.RoleAssignment
         self.manager = self.client.role_assignments
+        self.TEST_USER_SYSTEM_LIST = [{
+            'role': {
+                'id': self.TEST_ROLE_ID
+            },
+            'scope': {
+                'system': {
+                    'all': True
+                }
+            },
+            'user': {
+                'id': self.TEST_USER_ID
+            }
+        }]
+        self.TEST_GROUP_SYSTEM_LIST = [{
+            'role': {
+                'id': self.TEST_ROLE_ID
+            },
+            'scope': {
+                'system': {
+                    'all': True
+                }
+            },
+            'group': {
+                'id': self.TEST_GROUP_ID
+            }
+        }]
         self.TEST_USER_DOMAIN_LIST = [{
             'role': {
                 'id': self.TEST_ROLE_ID
@@ -65,7 +91,9 @@
 
         self.TEST_ALL_RESPONSE_LIST = (self.TEST_USER_PROJECT_LIST +
                                        self.TEST_GROUP_PROJECT_LIST +
-                                       self.TEST_USER_DOMAIN_LIST)
+                                       self.TEST_USER_DOMAIN_LIST +
+                                       self.TEST_USER_SYSTEM_LIST +
+                                       self.TEST_GROUP_SYSTEM_LIST)
 
     def _assert_returned_list(self, ref_list, returned_list):
         self.assertEqual(len(ref_list), len(returned_list))
@@ -150,6 +178,50 @@
         kwargs = {'scope.domain.id': self.TEST_DOMAIN_ID}
         self.assertQueryStringContains(**kwargs)
 
+    def test_system_assignment_list(self):
+        ref_list = self.TEST_USER_SYSTEM_LIST + self.TEST_GROUP_SYSTEM_LIST
+
+        self.stub_entity('GET',
+                         [self.collection_key, '?scope.system=all'],
+                         entity=ref_list)
+
+        returned_list = self.manager.list(system='all')
+        self._assert_returned_list(ref_list, returned_list)
+
+        kwargs = {'scope.system': 'all'}
+        self.assertQueryStringContains(**kwargs)
+
+    def test_system_assignment_list_for_user(self):
+        ref_list = self.TEST_USER_SYSTEM_LIST
+
+        self.stub_entity('GET',
+                         [self.collection_key,
+                          '?user.id=%s&scope.system=all' % self.TEST_USER_ID],
+                         entity=ref_list)
+
+        returned_list = self.manager.list(system='all', user=self.TEST_USER_ID)
+        self._assert_returned_list(ref_list, returned_list)
+
+        kwargs = {'scope.system': 'all', 'user.id': self.TEST_USER_ID}
+        self.assertQueryStringContains(**kwargs)
+
+    def test_system_assignment_list_for_group(self):
+        ref_list = self.TEST_GROUP_SYSTEM_LIST
+
+        self.stub_entity(
+            'GET',
+            [self.collection_key,
+             '?group.id=%s&scope.system=all' % self.TEST_GROUP_ID],
+            entity=ref_list)
+
+        returned_list = self.manager.list(
+            system='all', group=self.TEST_GROUP_ID
+        )
+        self._assert_returned_list(ref_list, returned_list)
+
+        kwargs = {'scope.system': 'all', 'group.id': self.TEST_GROUP_ID}
+        self.assertQueryStringContains(**kwargs)
+
     def test_group_assignments_list(self):
         ref_list = self.TEST_GROUP_PROJECT_LIST
         self.stub_entity('GET',
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/python-keystoneclient-3.14.0/keystoneclient/v3/application_credentials.py 
new/python-keystoneclient-3.15.0/keystoneclient/v3/application_credentials.py
--- 
old/python-keystoneclient-3.14.0/keystoneclient/v3/application_credentials.py   
    1970-01-01 01:00:00.000000000 +0100
+++ 
new/python-keystoneclient-3.15.0/keystoneclient/v3/application_credentials.py   
    2018-01-24 21:09:04.000000000 +0100
@@ -0,0 +1,171 @@
+# Copyright 2018 SUSE Linux GmbH
+#
+# 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 six
+
+from keystoneclient import base
+from keystoneclient import exceptions
+from keystoneclient.i18n import _
+from keystoneclient import utils
+
+
+class ApplicationCredential(base.Resource):
+    """Represents an Identity application credential.
+
+    Attributes:
+        * id: a uuid that identifies the application credential
+        * user: the user who owns the application credential
+        * name: application credential name
+        * secret: application credential secret
+        * description: application credential description
+        * expires_at: expiry time
+        * roles: role assignments on the project
+        * unrestricted: whether the application credential has restrictions
+            applied
+
+    """
+
+    pass
+
+
+class ApplicationCredentialManager(base.CrudManager):
+    """Manager class for manipulating Identity application credentials."""
+
+    resource_class = ApplicationCredential
+    collection_key = 'application_credentials'
+    key = 'application_credential'
+
+    def create(self, name, user=None, secret=None, description=None,
+               expires_at=None, roles=None,
+               unrestricted=False, **kwargs):
+        """Create a credential.
+
+        :param string name: application credential name
+        :param string user: User ID
+        :param secret: application credential secret
+        :param description: application credential description
+        :param datetime.datetime expires_at: expiry time
+        :param List roles: list of roles on the project. Maybe a list of IDs
+            or a list of dicts specifying role name and domain
+        :param bool unrestricted: whether the application credential has
+            restrictions applied
+
+        :returns: the created application credential
+        :rtype:
+            
:class:`keystoneclient.v3.application_credentials.ApplicationCredential`
+
+        """
+        user = user or self.client.user_id
+        self.base_url = '/users/%(user)s' % {'user': user}
+
+        # Convert roles list into list-of-dict API format
+        role_list = []
+        if roles:
+            if not isinstance(roles, list):
+                roles = [roles]
+            for role in roles:
+                if isinstance(role, six.string_types):
+                    role_list.extend([{'id': role}])
+                elif isinstance(role, dict):
+                    role_list.extend([role])
+                else:
+                    msg = (_("Roles must be a list of IDs or role dicts."))
+                    raise exceptions.CommandError(msg)
+
+        if not role_list:
+            role_list = None
+
+        # Convert datetime.datetime expires_at to iso format string
+        if expires_at:
+            expires_str = utils.isotime(at=expires_at, subsecond=True)
+        else:
+            expires_str = None
+
+        return super(ApplicationCredentialManager, self).create(
+            name=name,
+            secret=secret,
+            description=description,
+            expires_at=expires_str,
+            roles=role_list,
+            unrestricted=unrestricted,
+            **kwargs)
+
+    def get(self, application_credential, user=None):
+        """Retrieve an application credential.
+
+        :param application_credential: the credential to be retrieved from the
+            server
+        :type applicationcredential: str or
+            
:class:`keystoneclient.v3.application_credentials.ApplicationCredential`
+
+        :returns: the specified application credential
+        :rtype:
+            
:class:`keystoneclient.v3.application_credentials.ApplicationCredential`
+
+        """
+        user = user or self.client.user_id
+        self.base_url = '/users/%(user)s' % {'user': user}
+
+        return super(ApplicationCredentialManager, self).get(
+            application_credential_id=base.getid(application_credential))
+
+    def list(self, user=None, **kwargs):
+        """List application credentials.
+
+        :param string user: User ID
+
+        :returns: a list of application credentials
+        :rtype: list of
+            
:class:`keystoneclient.v3.application_credentials.ApplicationCredential`
+        """
+        user = user or self.client.user_id
+        self.base_url = '/users/%(user)s' % {'user': user}
+
+        return super(ApplicationCredentialManager, self).list(**kwargs)
+
+    def find(self, user=None, **kwargs):
+        """Find an application credential with attributes matching 
``**kwargs``.
+
+        :param string user: User ID
+
+        :returns: a list of matching application credentials
+        :rtype: list of
+            
:class:`keystoneclient.v3.application_credentials.ApplicationCredential`
+        """
+        user = user or self.client.user_id
+        self.base_url = '/users/%(user)s' % {'user': user}
+
+        return super(ApplicationCredentialManager, self).find(**kwargs)
+
+    def delete(self, application_credential, user=None):
+        """Delete an application credential.
+
+        :param application_credential: the application credential to be deleted
+        :type credential: str or
+            
:class:`keystoneclient.v3.application_credentials.ApplicationCredential`
+
+        :returns: response object with 204 status
+        :rtype: :class:`requests.models.Response`
+
+        """
+        user = user or self.client.user_id
+        self.base_url = '/users/%(user)s' % {'user': user}
+
+        return super(ApplicationCredentialManager, self).delete(
+            application_credential_id=base.getid(application_credential))
+
+    def update(self):
+        raise exceptions.MethodNotImplemented(
+            _('Application credentials are immutable, updating is not'
+              ' supported.'))
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/python-keystoneclient-3.14.0/keystoneclient/v3/client.py 
new/python-keystoneclient-3.15.0/keystoneclient/v3/client.py
--- old/python-keystoneclient-3.14.0/keystoneclient/v3/client.py        
2017-12-09 05:02:58.000000000 +0100
+++ new/python-keystoneclient-3.15.0/keystoneclient/v3/client.py        
2018-01-24 21:09:04.000000000 +0100
@@ -22,6 +22,7 @@
 from keystoneclient import exceptions
 from keystoneclient import httpclient
 from keystoneclient.i18n import _
+from keystoneclient.v3 import application_credentials
 from keystoneclient.v3 import auth
 from keystoneclient.v3.contrib import endpoint_filter
 from keystoneclient.v3.contrib import endpoint_policy
@@ -212,6 +213,9 @@
                 'deprecated as of the 1.7.0 release and may be removed in '
                 'the 2.0.0 release.', DeprecationWarning)
 
+        self.application_credentials = (
+            application_credentials.ApplicationCredentialManager(self._adapter)
+        )
         self.auth = auth.AuthManager(self._adapter)
         self.credentials = credentials.CredentialManager(self._adapter)
         self.ec2 = ec2.EC2Manager(self._adapter)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/python-keystoneclient-3.14.0/keystoneclient/v3/projects.py 
new/python-keystoneclient-3.15.0/keystoneclient/v3/projects.py
--- old/python-keystoneclient-3.14.0/keystoneclient/v3/projects.py      
2017-12-09 05:02:58.000000000 +0100
+++ new/python-keystoneclient-3.15.0/keystoneclient/v3/projects.py      
2018-01-24 21:08:40.000000000 +0100
@@ -14,6 +14,8 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+import six.moves.urllib as urllib
+
 from keystoneclient import base
 from keystoneclient import exceptions
 from keystoneclient.i18n import _
@@ -52,6 +54,24 @@
 
         return retval
 
+    def add_tag(self, tag):
+        self.manager.add_tag(self, tag)
+
+    def update_tags(self, tags):
+        return self.manager.update_tags(self, tags)
+
+    def delete_tag(self, tag):
+        self.manager.delete_tag(self, tag)
+
+    def delete_all_tags(self):
+        return self.manager.update_tags(self, [])
+
+    def list_tags(self):
+        return self.manager.list_tags(self)
+
+    def check_tag(self, tag):
+        return self.manager.check_tag(self, tag)
+
 
 class ProjectManager(base.CrudManager):
     """Manager class for manipulating Identity projects."""
@@ -101,17 +121,24 @@
                      assignments on.
         :type user: str or :class:`keystoneclient.v3.users.User`
         :param kwargs: any other attribute provided will filter projects on.
+                       Project tags filter keyword: ``tags``, ``tags_any``,
+                       ``not_tags``, and ``not_tags_any``. tag attribute type
+                       string. Pass in a comma separated string to filter
+                       with multiple tags.
 
         :returns: a list of projects.
         :rtype: list of :class:`keystoneclient.v3.projects.Project`
 
         """
         base_url = '/users/%s' % base.getid(user) if user else None
-        return super(ProjectManager, self).list(
+        projects = super(ProjectManager, self).list(
             base_url=base_url,
             domain_id=base.getid(domain),
             fallback_to_auth=True,
             **kwargs)
+        for p in projects:
+            p.tags = self._encode_tags(getattr(p, 'tags', []))
+        return projects
 
     def _check_not_parents_as_ids_and_parents_as_list(self, parents_as_ids,
                                                       parents_as_list):
@@ -174,7 +201,9 @@
         query = self.build_key_only_query(query_params)
         dict_args = {'project_id': base.getid(project)}
         url = self.build_url(dict_args_in_out=dict_args)
-        return self._get(url + query, self.key)
+        p = self._get(url + query, self.key)
+        p.tags = self._encode_tags(getattr(p, 'tags', []))
+        return p
 
     def update(self, project, name=None, domain=None, description=None,
                enabled=None, **kwargs):
@@ -213,3 +242,82 @@
         """
         return super(ProjectManager, self).delete(
             project_id=base.getid(project))
+
+    def _encode_tags(self, tags):
+        """Encode tags to non-unicode string in python2.
+
+        :param tags: list of unicode tags
+
+        :returns: List of strings
+        """
+        return [str(t) for t in tags]
+
+    def add_tag(self, project, tag):
+        """Add a tag to a project.
+
+        :param project: project to add a tag to.
+        :param tag: str name of tag.
+
+        """
+        url = "/projects/%s/tags/%s" % (base.getid(project),
+                                        urllib.parse.quote(tag))
+        self.client.put(url)
+
+    def update_tags(self, project, tags):
+        """Update tag list of a project.
+
+        Replaces current tag list with list specified in tags parameter.
+
+        :param project: project to update.
+        :param tags: list of str tag names to add to the project
+
+        :returns: list of tags
+
+        """
+        url = "/projects/%s/tags" % base.getid(project)
+        for tag in tags:
+            tag = urllib.parse.quote(tag)
+        resp, body = self.client.put(url, body={"tags": tags})
+        return body['tags']
+
+    def delete_tag(self, project, tag):
+        """Remove tag from project.
+
+        :param projectd: project to remove tag from.
+        :param tag: str name of tag to remove from project
+
+        """
+        self._delete(
+            "/projects/%s/tags/%s" % (base.getid(project),
+                                      urllib.parse.quote(tag)))
+
+    def list_tags(self, project):
+        """List tags associated with project.
+
+        :param project: project to list tags for.
+
+        :returns: list of str tag names
+
+        """
+        url = "/projects/%s/tags" % base.getid(project)
+        resp, body = self.client.get(url)
+        return self._encode_tags(body['tags'])
+
+    def check_tag(self, project, tag):
+        """Check if tag is associated with project.
+
+        :param project: project to check tags for.
+        :param tag: str name of tag
+
+        :returns: true if tag is associated, false otherwise
+
+        """
+        url = "/projects/%s/tags/%s" % (base.getid(project),
+                                        urllib.parse.quote(tag))
+        try:
+            self.client.head(url)
+            # no errors means found the tag
+            return True
+        except exceptions.NotFound:
+            # 404 means tag not in project
+            return False
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/python-keystoneclient-3.14.0/keystoneclient/v3/role_assignments.py 
new/python-keystoneclient-3.15.0/keystoneclient/v3/role_assignments.py
--- old/python-keystoneclient-3.14.0/keystoneclient/v3/role_assignments.py      
2017-12-09 05:02:58.000000000 +0100
+++ new/python-keystoneclient-3.15.0/keystoneclient/v3/role_assignments.py      
2018-01-24 21:09:04.000000000 +0100
@@ -46,9 +46,25 @@
             msg = _('Specify either a domain or project, not both')
             raise exceptions.ValidationError(msg)
 
-    def list(self, user=None, group=None, project=None, domain=None, role=None,
-             effective=False, os_inherit_extension_inherited_to=None,
-             include_subtree=False, include_names=False):
+    def _check_not_system_and_domain(self, system, domain):
+        if system and domain:
+            msg = _('Specify either system or domain, not both')
+            raise exceptions.ValidationError(msg)
+
+    def _check_not_system_and_project(self, system, project):
+        if system and project:
+            msg = _('Specify either system or project, not both')
+            raise exceptions.ValidationError(msg)
+
+    def _check_system_value(self, system):
+        if system and system != 'all':
+            msg = _("Only a system scope of 'all' is currently supported")
+            raise exceptions.ValidationError(msg)
+
+    def list(self, user=None, group=None, project=None, domain=None,
+             system=False, role=None, effective=False,
+             os_inherit_extension_inherited_to=None, include_subtree=False,
+             include_names=False):
         """List role assignments.
 
         If no arguments are provided, all role assignments in the
@@ -64,6 +80,8 @@
                         (optional)
         :param domain: Domain to be used as query
                        filter. (optional)
+        :param system: Boolean to be used to filter system assignments.
+                       (optional)
         :param role: Role to be used as query filter. (optional)
         :param boolean effective: return effective role
                                   assignments. (optional)
@@ -76,6 +94,9 @@
         """
         self._check_not_user_and_group(user, group)
         self._check_not_domain_and_project(domain, project)
+        self._check_not_system_and_domain(system, domain)
+        self._check_not_system_and_project(system, project)
+        self._check_system_value(system)
 
         query_params = {}
         if user:
@@ -86,6 +107,8 @@
             query_params['scope.project.id'] = base.getid(project)
         if domain:
             query_params['scope.domain.id'] = base.getid(domain)
+        if system:
+            query_params['scope.system'] = system
         if role:
             query_params['role.id'] = base.getid(role)
         if effective:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/python-keystoneclient-3.14.0/keystoneclient/v3/roles.py 
new/python-keystoneclient-3.15.0/keystoneclient/v3/roles.py
--- old/python-keystoneclient-3.14.0/keystoneclient/v3/roles.py 2017-12-09 
05:02:58.000000000 +0100
+++ new/python-keystoneclient-3.15.0/keystoneclient/v3/roles.py 2018-01-24 
21:09:04.000000000 +0100
@@ -54,7 +54,7 @@
     key = 'role'
     deprecation_msg = 'keystoneclient.v3.roles.InferenceRuleManager'
 
-    def _role_grants_base_url(self, user, group, domain, project,
+    def _role_grants_base_url(self, user, group, system, domain, project,
                               use_inherit_extension):
         # When called, we have already checked that only one of user & group
         # and one of domain & project have been specified
@@ -66,6 +66,18 @@
         elif domain:
             params['domain_id'] = base.getid(domain)
             base_url = '/domains/%(domain_id)s'
+        elif system:
+            if system == 'all':
+                base_url = '/system'
+            else:
+                # NOTE(lbragstad): If we've made it this far, a user is
+                # attempting to do something with system scope that isn't
+                # supported yet (e.g. 'all' is currently the only supported
+                # system scope). In the future that may change but until then
+                # we should fail like we would if a user provided a bogus
+                # project name or domain ID.
+                msg = _("Only a system scope of 'all' is currently supported")
+                raise exceptions.ValidationError(msg)
 
         if use_inherit_extension:
             base_url = '/OS-INHERIT' + base_url
@@ -79,13 +91,26 @@
 
         return base_url % params
 
-    def _require_domain_xor_project(self, domain, project):
-        if domain and project:
-            msg = _('Specify either a domain or project, not both')
-            raise exceptions.ValidationError(msg)
-        elif not (domain or project):
-            msg = _('Must specify either a domain or project')
-            raise exceptions.ValidationError(msg)
+    def _enforce_mutually_exclusive_group(self, system, domain, project):
+        if not system:
+            if domain and project:
+                msg = _('Specify either a domain or project, not both')
+                raise exceptions.ValidationError(msg)
+            elif not (domain or project):
+                msg = _('Must specify either system, domain, or project')
+                raise exceptions.ValidationError(msg)
+        elif system:
+            if domain and project:
+                msg = _(
+                    'Specify either system, domain, or project, not all three.'
+                )
+                raise exceptions.ValidationError(msg)
+            if domain:
+                msg = _('Specify either system or a domain, not both')
+                raise exceptions.ValidationError(msg)
+            if project:
+                msg = _('Specify either a system or project, not both')
+                raise exceptions.ValidationError(msg)
 
     def _require_user_xor_group(self, user, group):
         if user and group:
@@ -130,7 +155,7 @@
         """
         return super(RoleManager, self).get(role_id=base.getid(role))
 
-    def list(self, user=None, group=None, domain=None,
+    def list(self, user=None, group=None, system=None, domain=None,
              project=None, os_inherit_extension_inherited=False, **kwargs):
         """List roles and role grants.
 
@@ -143,12 +168,12 @@
                       User and group are mutually exclusive.
         :type group: str or :class:`keystoneclient.v3.groups.Group`
         :param domain: filter in role grants on the specified domain. Either
-                       user or group must be specified. Project and domain
-                       are mutually exclusive.
+                       user or group must be specified. Project, domain, and
+                       system are mutually exclusive.
         :type domain: str or :class:`keystoneclient.v3.domains.Domain`
         :param project: filter in role grants on the specified project. Either
-                       user or group must be specified. Project and domain
-                       are mutually exclusive.
+                       user or group must be specified. Project, domain and
+                       system are mutually exclusive.
         :type project: str or :class:`keystoneclient.v3.projects.Project`
         :param bool os_inherit_extension_inherited: OS-INHERIT will be used.
                                                     It provides the ability for
@@ -166,10 +191,12 @@
             kwargs['tail'] = '/inherited_to_projects'
         if user or group:
             self._require_user_xor_group(user, group)
-            self._require_domain_xor_project(domain, project)
+            self._enforce_mutually_exclusive_group(system, domain, project)
 
             base_url = self._role_grants_base_url(
-                user, group, domain, project, os_inherit_extension_inherited)
+                user, group, system, domain, project,
+                os_inherit_extension_inherited
+            )
             return super(RoleManager, self).list(base_url=base_url,
                                                  **kwargs)
 
@@ -208,8 +235,8 @@
         return super(RoleManager, self).delete(
             role_id=base.getid(role))
 
-    def grant(self, role, user=None, group=None, domain=None, project=None,
-              os_inherit_extension_inherited=False, **kwargs):
+    def grant(self, role, user=None, group=None, system=None, domain=None,
+              project=None, os_inherit_extension_inherited=False, **kwargs):
         """Grant a role to a user or group on a domain or project.
 
         :param role: the role to be granted on the server.
@@ -222,13 +249,16 @@
                       resource. Domain or project must be specified.
                       User and group are mutually exclusive.
         :type group: str or :class:`keystoneclient.v3.groups.Group`
+        :param system: system information to grant the role on. Project,
+                       domain, and system are mutually exclusive.
+        :type system: str
         :param domain: the domain in which the role will be granted. Either
-                       user or group must be specified. Project and domain
-                       are mutually exclusive.
+                       user or group must be specified. Project, domain, and
+                       system are mutually exclusive.
         :type domain: str or :class:`keystoneclient.v3.domains.Domain`
         :param project: the project in which the role will be granted. Either
-                       user or group must be specified. Project and domain
-                       are mutually exclusive.
+                       user or group must be specified. Project, domain, and
+                       system are mutually exclusive.
         :type project: str or :class:`keystoneclient.v3.projects.Project`
         :param bool os_inherit_extension_inherited: OS-INHERIT will be used.
                                                     It provides the ability for
@@ -242,20 +272,21 @@
         :rtype: :class:`keystoneclient.v3.roles.Role`
 
         """
-        self._require_domain_xor_project(domain, project)
+        self._enforce_mutually_exclusive_group(system, domain, project)
         self._require_user_xor_group(user, group)
 
         if os_inherit_extension_inherited:
             kwargs['tail'] = '/inherited_to_projects'
 
         base_url = self._role_grants_base_url(
-            user, group, domain, project, os_inherit_extension_inherited)
+            user, group, system, domain, project,
+            os_inherit_extension_inherited)
         return super(RoleManager, self).put(base_url=base_url,
                                             role_id=base.getid(role),
                                             **kwargs)
 
-    def check(self, role, user=None, group=None, domain=None, project=None,
-              os_inherit_extension_inherited=False, **kwargs):
+    def check(self, role, user=None, group=None, system=None, domain=None,
+              project=None, os_inherit_extension_inherited=False, **kwargs):
         """Check if a user or group has a role on a domain or project.
 
         :param user: check for role grants for the specified user on a
@@ -266,13 +297,16 @@
                       resource. Domain or project must be specified.
                       User and group are mutually exclusive.
         :type group: str or :class:`keystoneclient.v3.groups.Group`
+        :param system: check for role  grants on the system. Project, domain,
+                       and system are mutually exclusive.
+        :type system: str
         :param domain: check for role grants on the specified domain. Either
-                       user or group must be specified. Project and domain
-                       are mutually exclusive.
+                       user or group must be specified. Project, domain, and
+                       system are mutually exclusive.
         :type domain: str or :class:`keystoneclient.v3.domains.Domain`
         :param project: check for role grants on the specified project. Either
-                       user or group must be specified. Project and domain
-                       are mutually exclusive.
+                       user or group must be specified. Project, domain, and
+                       system are mutually exclusive.
         :type project: str or :class:`keystoneclient.v3.projects.Project`
         :param bool os_inherit_extension_inherited: OS-INHERIT will be used.
                                                     It provides the ability for
@@ -290,22 +324,23 @@
         :rtype: :class:`requests.models.Response`
 
         """
-        self._require_domain_xor_project(domain, project)
+        self._enforce_mutually_exclusive_group(system, domain, project)
         self._require_user_xor_group(user, group)
 
         if os_inherit_extension_inherited:
             kwargs['tail'] = '/inherited_to_projects'
 
         base_url = self._role_grants_base_url(
-            user, group, domain, project, os_inherit_extension_inherited)
+            user, group, system, domain, project,
+            os_inherit_extension_inherited)
         return super(RoleManager, self).head(
             base_url=base_url,
             role_id=base.getid(role),
             os_inherit_extension_inherited=os_inherit_extension_inherited,
             **kwargs)
 
-    def revoke(self, role, user=None, group=None, domain=None, project=None,
-               os_inherit_extension_inherited=False, **kwargs):
+    def revoke(self, role, user=None, group=None, system=None, domain=None,
+               project=None, os_inherit_extension_inherited=False, **kwargs):
         """Revoke a role from a user or group on a domain or project.
 
         :param user: revoke role grants for the specified user on a
@@ -316,13 +351,16 @@
                       resource. Domain or project must be specified.
                       User and group are mutually exclusive.
         :type group: str or :class:`keystoneclient.v3.groups.Group`
+        :param system: revoke role grants on the system. Project, domain, and
+                       system are mutually exclusive.
+        :type system: str
         :param domain: revoke role grants on the specified domain. Either
-                       user or group must be specified. Project and domain
-                       are mutually exclusive.
+                       user or group must be specified. Project, domain, and
+                       system are mutually exclusive.
         :type domain: str or :class:`keystoneclient.v3.domains.Domain`
         :param project: revoke role grants on the specified project. Either
-                       user or group must be specified. Project and domain
-                       are mutually exclusive.
+                       user or group must be specified. Project, domain, and
+                       system are mutually exclusive.
         :type project: str or :class:`keystoneclient.v3.projects.Project`
         :param bool os_inherit_extension_inherited: OS-INHERIT will be used.
                                                     It provides the ability for
@@ -336,14 +374,15 @@
         :rtype: list of :class:`keystoneclient.v3.roles.Role`
 
         """
-        self._require_domain_xor_project(domain, project)
+        self._enforce_mutually_exclusive_group(system, domain, project)
         self._require_user_xor_group(user, group)
 
         if os_inherit_extension_inherited:
             kwargs['tail'] = '/inherited_to_projects'
 
         base_url = self._role_grants_base_url(
-            user, group, domain, project, os_inherit_extension_inherited)
+            user, group, system, domain, project,
+            os_inherit_extension_inherited)
         return super(RoleManager, self).delete(
             base_url=base_url,
             role_id=base.getid(role),
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/python-keystoneclient-3.14.0/python_keystoneclient.egg-info/PKG-INFO 
new/python-keystoneclient-3.15.0/python_keystoneclient.egg-info/PKG-INFO
--- old/python-keystoneclient-3.14.0/python_keystoneclient.egg-info/PKG-INFO    
2017-12-09 05:06:22.000000000 +0100
+++ new/python-keystoneclient-3.15.0/python_keystoneclient.egg-info/PKG-INFO    
2018-01-24 21:10:42.000000000 +0100
@@ -1,6 +1,6 @@
 Metadata-Version: 1.1
 Name: python-keystoneclient
-Version: 3.14.0
+Version: 3.15.0
 Summary: Client Library for OpenStack Identity
 Home-page: https://docs.openstack.org/python-keystoneclient/latest/
 Author: OpenStack
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/python-keystoneclient-3.14.0/python_keystoneclient.egg-info/SOURCES.txt 
new/python-keystoneclient-3.15.0/python_keystoneclient.egg-info/SOURCES.txt
--- old/python-keystoneclient-3.14.0/python_keystoneclient.egg-info/SOURCES.txt 
2017-12-09 05:06:23.000000000 +0100
+++ new/python-keystoneclient-3.15.0/python_keystoneclient.egg-info/SOURCES.txt 
2018-01-24 21:10:43.000000000 +0100
@@ -16,6 +16,7 @@
 tox.ini
 doc/.gitignore
 doc/Makefile
+doc/requirements.txt
 doc/source/conf.py
 doc/source/history.rst
 doc/source/index.rst
@@ -179,6 +180,7 @@
 keystoneclient/tests/unit/v3/client_fixtures.py
 keystoneclient/tests/unit/v3/saml2_fixtures.py
 keystoneclient/tests/unit/v3/test_access.py
+keystoneclient/tests/unit/v3/test_application_credentials.py
 keystoneclient/tests/unit/v3/test_auth.py
 keystoneclient/tests/unit/v3/test_auth_manager.py
 keystoneclient/tests/unit/v3/test_auth_oidc.py
@@ -222,6 +224,7 @@
 keystoneclient/v2_0/tokens.py
 keystoneclient/v2_0/users.py
 keystoneclient/v3/__init__.py
+keystoneclient/v3/application_credentials.py
 keystoneclient/v3/auth.py
 keystoneclient/v3/client.py
 keystoneclient/v3/credentials.py
@@ -271,6 +274,7 @@
 python_keystoneclient.egg-info/top_level.txt
 releasenotes/notes/.placeholder
 releasenotes/notes/Add-allow-expired-flag-to-validate-25b8914f4deb359b.yaml
+releasenotes/notes/bp-application-credentials-27728ded876d7d5a.yaml
 releasenotes/notes/bp-domain-config-9566e672a98f4e7f.yaml
 
releasenotes/notes/bp-pci-dss-query-password-expired-users-b0c4b1bbdcf33f16.yaml
 releasenotes/notes/bug-1616105-cc8b85eb056e99e2.yaml
@@ -280,6 +284,7 @@
 releasenotes/notes/implied_roles-ea39d3c3d998d482.yaml
 releasenotes/notes/ksc_2.1.0-739ded9c4c3f8aaa.yaml
 releasenotes/notes/list_role_assignment_names-7e1b7eb8c2d22d7c.yaml
+releasenotes/notes/project-tags-1f8a32d389951e7a.yaml
 releasenotes/notes/remove-credentials-data-46ab3c3c248047cf.yaml
 releasenotes/notes/remove-middleware-eef8c40117b465aa.yaml
 releasenotes/notes/remove_apiclient_exceptions-0cd5c8d16aa09a22.yaml
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/python-keystoneclient-3.14.0/python_keystoneclient.egg-info/pbr.json 
new/python-keystoneclient-3.15.0/python_keystoneclient.egg-info/pbr.json
--- old/python-keystoneclient-3.14.0/python_keystoneclient.egg-info/pbr.json    
2017-12-09 05:06:22.000000000 +0100
+++ new/python-keystoneclient-3.15.0/python_keystoneclient.egg-info/pbr.json    
2018-01-24 21:10:42.000000000 +0100
@@ -1 +1 @@
-{"git_version": "2bea645", "is_release": true}
\ No newline at end of file
+{"git_version": "1e8c930", "is_release": true}
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/python-keystoneclient-3.14.0/python_keystoneclient.egg-info/requires.txt 
new/python-keystoneclient-3.15.0/python_keystoneclient.egg-info/requires.txt
--- 
old/python-keystoneclient-3.14.0/python_keystoneclient.egg-info/requires.txt    
    2017-12-09 05:06:22.000000000 +0100
+++ 
new/python-keystoneclient-3.15.0/python_keystoneclient.egg-info/requires.txt    
    2018-01-24 21:10:42.000000000 +0100
@@ -4,7 +4,7 @@
 oslo.config>=5.1.0
 oslo.i18n>=3.15.3
 oslo.serialization!=2.19.1,>=2.18.0
-oslo.utils>=3.31.0
+oslo.utils>=3.33.0
 requests>=2.14.2
 six>=1.10.0
 stevedore>=1.20.0
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/python-keystoneclient-3.14.0/releasenotes/notes/bp-application-credentials-27728ded876d7d5a.yaml
 
new/python-keystoneclient-3.15.0/releasenotes/notes/bp-application-credentials-27728ded876d7d5a.yaml
--- 
old/python-keystoneclient-3.14.0/releasenotes/notes/bp-application-credentials-27728ded876d7d5a.yaml
        1970-01-01 01:00:00.000000000 +0100
+++ 
new/python-keystoneclient-3.15.0/releasenotes/notes/bp-application-credentials-27728ded876d7d5a.yaml
        2018-01-24 21:09:04.000000000 +0100
@@ -0,0 +1,8 @@
+---
+features:
+  - |
+    Adds support for creating, reading, and deleting application credentials.
+    With application credentials, a user can grant their applications limited
+    access to their cloud resources. Applications can use keystoneauth with
+    the `v3applicationcredential` auth plugin to authenticate with keystone
+    without needing the user's password.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/python-keystoneclient-3.14.0/releasenotes/notes/project-tags-1f8a32d389951e7a.yaml
 
new/python-keystoneclient-3.15.0/releasenotes/notes/project-tags-1f8a32d389951e7a.yaml
--- 
old/python-keystoneclient-3.14.0/releasenotes/notes/project-tags-1f8a32d389951e7a.yaml
      1970-01-01 01:00:00.000000000 +0100
+++ 
new/python-keystoneclient-3.15.0/releasenotes/notes/project-tags-1f8a32d389951e7a.yaml
      2018-01-24 21:08:40.000000000 +0100
@@ -0,0 +1,8 @@
+---
+features:
+  - |
+    [`blueprint project-tags 
<https://blueprints.launchpad.net/keystone/+spec/project-tags>`_]
+    The keystoneclient now supports project tags feature in keystone.  This
+    allows operators to use the client to associate tags to a project,
+    retrieve tags associated with a project, delete tags associated with a
+    project, and filter projects based on tags.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python-keystoneclient-3.14.0/requirements.txt 
new/python-keystoneclient-3.15.0/requirements.txt
--- old/python-keystoneclient-3.14.0/requirements.txt   2017-12-09 
05:02:58.000000000 +0100
+++ new/python-keystoneclient-3.15.0/requirements.txt   2018-01-24 
21:08:40.000000000 +0100
@@ -9,7 +9,7 @@
 oslo.config>=5.1.0 # Apache-2.0
 oslo.i18n>=3.15.3 # Apache-2.0
 oslo.serialization!=2.19.1,>=2.18.0 # Apache-2.0
-oslo.utils>=3.31.0 # Apache-2.0
+oslo.utils>=3.33.0 # Apache-2.0
 requests>=2.14.2 # Apache-2.0
 six>=1.10.0 # MIT
 stevedore>=1.20.0 # Apache-2.0
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python-keystoneclient-3.14.0/test-requirements.txt 
new/python-keystoneclient-3.15.0/test-requirements.txt
--- old/python-keystoneclient-3.14.0/test-requirements.txt      2017-12-09 
05:02:58.000000000 +0100
+++ new/python-keystoneclient-3.15.0/test-requirements.txt      2018-01-24 
21:08:40.000000000 +0100
@@ -11,11 +11,8 @@
 lxml!=3.7.0,>=3.4.1 # BSD
 mock>=2.0.0 # BSD
 oauthlib>=0.6.0 # BSD
-openstackdocstheme>=1.17.0 # Apache-2.0
-oslotest>=1.10.0 # Apache-2.0
-reno>=2.5.0 # Apache-2.0
+oslotest>=3.2.0 # Apache-2.0
 requests-mock>=1.1.0 # Apache-2.0
-sphinx>=1.6.2 # BSD
 tempest>=17.1.0 # Apache-2.0
 testrepository>=0.0.18 # Apache-2.0/BSD
 testresources>=2.0.0 # Apache-2.0/BSD
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python-keystoneclient-3.14.0/tox.ini 
new/python-keystoneclient-3.15.0/tox.ini
--- old/python-keystoneclient-3.14.0/tox.ini    2017-12-09 05:02:58.000000000 
+0100
+++ new/python-keystoneclient-3.15.0/tox.ini    2018-01-24 21:08:40.000000000 
+0100
@@ -55,11 +55,12 @@
 exclude = .venv,.tox,dist,doc,*egg,build
 
 [testenv:docs]
-commands=
-    python setup.py build_sphinx
+commands = python setup.py build_sphinx
+deps = -r{toxinidir}/doc/requirements.txt
 
 [testenv:releasenotes]
 commands = sphinx-build -a -E -W -d releasenotes/build/doctrees -b html 
releasenotes/source releasenotes/build/html
+deps = -r{toxinidir}/doc/requirements.txt
 
 [hacking]
 import_exceptions =


Reply via email to