Repository: openshift/openshift-restclient-python

Issue:
openshift dynamic client is now case sensitive
Using `kind=namespace` (for example) used to work but no longer does

See https://github.com/ansible/ansible/issues/48137

This breaks backwards compatibility.

Discussion:
fabianvf: /cherry-pick release-0.8

Patch:
diff --git a/.tito/packages/python-openshift b/.tito/packages/python-openshift
index f18cd6f..3f8c461 100644
--- a/.tito/packages/python-openshift
+++ b/.tito/packages/python-openshift
@@ -1,1 +1,1 @@
-0.6.2-12 ./
+0.8.0-1 ./
diff --git a/.tito/releasers.conf b/.tito/releasers.conf
index 2b4c815..f140a70 100644
--- a/.tito/releasers.conf
+++ b/.tito/releasers.conf
@@ -15,4 +15,4 @@ builder.test = 1
 
 [asb-brew]
 releaser = tito.release.DistGitReleaser
-branches = rhaos-3.11-asb-rhel-7
+branches = rhaos-4.0-asb-rhel-7
diff --git a/openshift/dynamic/client.py b/openshift/dynamic/client.py
index 5a51558..7bf55b5 100755
--- a/openshift/dynamic/client.py
+++ b/openshift/dynamic/client.py
@@ -517,10 +517,14 @@ class ResourceContainer(object):
             on api_version, that resource will be returned.
         """
         results = self.search(**kwargs)
+        # If there are multiple matches, prefer exact matches on api_version
         if len(results) > 1 and kwargs.get('api_version'):
             results = [
                 result for result in results if result.group_version == kwargs['api_version']
             ]
+        # If there are multiple matches, prefer non-List kinds
+        if len(results) > 1 and not all([isinstance(x, ResourceList) for x in results]):
+            results = [result for result in results if not isinstance(result, ResourceList)]
         if len(results) == 1:
             return results[0]
         elif not results:
diff --git a/python-openshift.spec b/python-openshift.spec
index 221f5b6..12340a1 100644
--- a/python-openshift.spec
+++ b/python-openshift.spec
@@ -3,8 +3,8 @@
 %global library openshift
 
 Name:       python-%{library}
-Version:    0.7.0
-Release:    12%{?dist}
+Version:    0.8.0
+Release:    1%{?dist}
 Summary:    Python client for the OpenShift API  
 License:    MIT
 URL:        https://github.com/openshift/openshift-restclient-python
@@ -131,6 +131,28 @@ sed -i -e "s/extract_requirements('requirements.txt')/REQUIRES/g" setup.py
 %endif # with_python3
 
 %changelog
+* Tue Nov 06 2018 Jason Montleon <jmontleo@redhat.com> 0.8.0-1
+- Fix tag condition (fabian@fabianism.us)
+- Add watch to dynamic client (#221) (fabian@fabianism.us)
+- Pin flake8 (fabian@fabianism.us)
+- Do not decode response data in Python2 (#225)
+  (16732494+wallecan@users.noreply.github.com)
+- ResourceContainer does not contain delete method (#227)
+  (mosonkonrad@gmail.com)
+- Add basic documentation for dynamic client verbs to README (#222)
+  (fabian@fabianism.us)
+- Add support for *List kinds (#213) (fabian@fabianism.us)
+- Add validate helper function (#199) (will@thames.id.au)
+- DynamicApiError: add a summary method (#211) (pierre-louis@libregerbil.fr)
+- Allow less strict kubernetes version requirements (#207) (will@thames.id.au)
+- Add behavior-based tests for dynamic client (#208) (fabian@fabianism.us)
+- Provide 'append_hash' for ConfigMaps and Secrets (#196) (will@thames.id.au)
+- Allow creates on subresources properly (#201) (fabian@fabianism.us)
+- Rename async to async_req for compatibility with python3 and kubernetes 7
+  (#197) (fabian@fabianism.us)
+- Update kube_config to support concurrent clusters (#193)
+  (tdecacqu@redhat.com)
+
 * Mon Aug 06 2018 David Zager <david.j.zager@gmail.com> 0.6.2-12
 - Fix decode issue (#192) (lostonamountain@gmail.com)
 - b64encode expects bytes not string (fridolin@redhat.com)


Test patch:
diff --git a/test/unit/test_resource_container.py b/test/unit/test_resource_container.py
new file mode 100644
index 0000000..5dd8657
--- /dev/null
+++ b/test/unit/test_resource_container.py
@@ -0,0 +1,82 @@
+import pytest
+
+from kubernetes.client import ApiClient
+
+from openshift.dynamic import DynamicClient, Resource, ResourceList
+
+
+@pytest.fixture(scope='module')
+def mock_namespace():
+    return Resource(
+        api_version='v1',
+        kind='Namespace',
+        name='namespaces',
+        namespaced=False,
+        preferred=True,
+        prefix='api',
+        shorter_names=['ns'],
+        shortNames=['ns'],
+        singularName='namespace',
+        verbs=['create', 'delete', 'get', 'list', 'patch', 'update', 'watch']
+    )
+
+
+@pytest.fixture(scope='module')
+def mock_namespace_list(mock_namespace):
+    return ResourceList(mock_namespace)
+
+@pytest.fixture(scope='function', autouse=True)
+def setup_client_monkeypatch(monkeypatch, mock_namespace, mock_namespace_list):
+
+    def mock_load_server_info(self):
+        self.__version = {'kubernetes': 'mock-k8s-version'}
+
+    def mock_parse_api_groups(self):
+            return {
+            'api': {
+                '': {
+                    'v1': {
+                        'Namespace': mock_namespace,
+                        'NamespaceList': mock_namespace_list
+                    }
+                }
+            }
+        }
+
+    monkeypatch.setattr(DynamicClient, '_load_server_info', mock_load_server_info)
+    monkeypatch.setattr(DynamicClient, 'parse_api_groups', mock_parse_api_groups)
+
+
+@pytest.fixture()
+def client():
+    return DynamicClient(ApiClient())
+
+
+@pytest.mark.parametrize(("attribute", "value"), [
+    ('name', 'namespaces'),
+    ('singular_name', 'namespace'),
+    ('short_names', ['ns'])
+])
+def test_search_returns_single_and_list(client, mock_namespace, mock_namespace_list, attribute, value):
+    resources = client.resources.search(**{'api_version':'v1', attribute: value})
+
+    assert len(resources) == 2
+    assert mock_namespace in resources
+    assert mock_namespace_list in resources
+
+@pytest.mark.parametrize(("attribute", "value"), [
+    ('kind', 'Namespace'),
+    ('name', 'namespaces'),
+    ('singular_name', 'namespace'),
+    ('short_names', ['ns'])
+])
+def test_get_returns_only_single(client, mock_namespace, attribute, value):
+    resource = client.resources.get(**{'api_version':'v1', attribute: value})
+
+    assert resource == mock_namespace
+
+
+def test_get_namespace_list_kind(client, mock_namespace_list):
+    resource = client.resources.get(api_version='v1', kind='NamespaceList')
+
+    assert resource == mock_namespace_list