Skip to content

Commit 32737c0

Browse files
Merge pull request #411 from fabianvf/rebase-on-upstream
Use upstream implementation as the base, maintain only diffs
2 parents 9cd1f8f + 2788a55 commit 32737c0

File tree

6 files changed

+35
-924
lines changed

6 files changed

+35
-924
lines changed

.travis.yml

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,7 @@ cache:
55
- pip
66
language: python
77
python:
8-
- '2.7'
9-
- '3.8'
8+
- '3.9'
109
env:
1110
global:
1211
- COVERALLS_PARALLEL=true
@@ -20,21 +19,21 @@ after_success:
2019
jobs:
2120
include:
2221
- stage: lint
23-
python: '2.7'
22+
python: '3.9'
2423
install:
2524
- pip install tox-travis
2625
script: tox -e py27-lint
2726
env:
2827
- TEST_SUITE=lint
29-
- python: '3.8'
28+
- python: '3.9'
3029
install:
3130
- pip install tox-travis
3231
script: tox -e py35-lint
3332
env:
3433
- TEST_SUITE=lint
3534
- stage: deploy
3635
script: skip
37-
python: '3.8'
36+
python: '3.9'
3837
deploy:
3938
provider: pypi
4039
user: openshift
@@ -45,7 +44,7 @@ jobs:
4544
repo: openshift/openshift-restclient-python
4645
condition: "$TRAVIS_TAG =~ ^v[0-9]+\\.[0-9]+\\.[0-9]+(([ab]|dev|rc)[0-9]+)?$"
4746
- stage: test-deploy
48-
python: '3.8'
47+
python: '3.9'
4948
script: python -c "import openshift ; print(openshift.__version__)"
5049
install:
5150
- pip install openshift

openshift/dynamic/client.py

Lines changed: 6 additions & 234 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,13 @@
1-
import six
2-
import json
3-
4-
from kubernetes import watch
5-
from kubernetes.client.rest import ApiException
1+
from kubernetes.dynamic.client import meta_request # noqa
2+
from kubernetes.dynamic.client import DynamicClient as K8sDynamicClient
63

74
from .apply import apply
85
from .discovery import EagerDiscoverer, LazyDiscoverer
9-
from .exceptions import api_exception, KubernetesValidateMissing, ApplyException
106
from .resource import Resource, ResourceList, Subresource, ResourceInstance, ResourceField
7+
from .exceptions import ApplyException
118

129
try:
13-
import kubernetes_validate
10+
import kubernetes_validate # noqa
1411
HAS_KUBERNETES_VALIDATE = True
1512
except ImportError:
1613
HAS_KUBERNETES_VALIDATE = False
@@ -33,103 +30,14 @@ class VersionNotSupportedError(NotImplementedError):
3330
]
3431

3532

36-
def meta_request(func):
37-
""" Handles parsing response structure and translating API Exceptions """
38-
def inner(self, *args, **kwargs):
39-
serialize_response = kwargs.pop('serialize', True)
40-
serializer = kwargs.pop('serializer', ResourceInstance)
41-
try:
42-
resp = func(self, *args, **kwargs)
43-
except ApiException as e:
44-
raise api_exception(e)
45-
if serialize_response:
46-
try:
47-
if six.PY2:
48-
return serializer(self, json.loads(resp.data))
49-
return serializer(self, json.loads(resp.data.decode('utf8')))
50-
except ValueError:
51-
if six.PY2:
52-
return resp.data
53-
return resp.data.decode('utf8')
54-
return resp
55-
56-
return inner
57-
58-
59-
class DynamicClient(object):
33+
class DynamicClient(K8sDynamicClient):
6034
""" A kubernetes client that dynamically discovers and interacts with
6135
the kubernetes API
6236
"""
6337

6438
def __init__(self, client, cache_file=None, discoverer=None):
65-
# Setting default here to delay evaluation of LazyDiscoverer class
66-
# until constructor is called
6739
discoverer = discoverer or LazyDiscoverer
68-
69-
self.client = client
70-
self.configuration = client.configuration
71-
self.__discoverer = discoverer(self, cache_file)
72-
73-
@property
74-
def resources(self):
75-
return self.__discoverer
76-
77-
@property
78-
def version(self):
79-
return self.__discoverer.version
80-
81-
def ensure_namespace(self, resource, namespace, body):
82-
namespace = namespace or body.get('metadata', {}).get('namespace')
83-
if not namespace:
84-
raise ValueError("Namespace is required for {}.{}".format(resource.group_version, resource.kind))
85-
return namespace
86-
87-
def serialize_body(self, body):
88-
if hasattr(body, 'to_dict'):
89-
return body.to_dict()
90-
return body or {}
91-
92-
def get(self, resource, name=None, namespace=None, **kwargs):
93-
path = resource.path(name=name, namespace=namespace)
94-
return self.request('get', path, **kwargs)
95-
96-
def create(self, resource, body=None, namespace=None, **kwargs):
97-
body = self.serialize_body(body)
98-
if resource.namespaced:
99-
namespace = self.ensure_namespace(resource, namespace, body)
100-
path = resource.path(namespace=namespace)
101-
return self.request('post', path, body=body, **kwargs)
102-
103-
def delete(self, resource, name=None, namespace=None, label_selector=None, field_selector=None, **kwargs):
104-
if not (name or label_selector or field_selector):
105-
raise ValueError("At least one of name|label_selector|field_selector is required")
106-
if resource.namespaced and not (label_selector or field_selector or namespace):
107-
raise ValueError("At least one of namespace|label_selector|field_selector is required")
108-
path = resource.path(name=name, namespace=namespace)
109-
return self.request('delete', path, label_selector=label_selector, field_selector=field_selector, **kwargs)
110-
111-
def replace(self, resource, body=None, name=None, namespace=None, **kwargs):
112-
body = self.serialize_body(body)
113-
name = name or body.get('metadata', {}).get('name')
114-
if not name:
115-
raise ValueError("name is required to replace {}.{}".format(resource.group_version, resource.kind))
116-
if resource.namespaced:
117-
namespace = self.ensure_namespace(resource, namespace, body)
118-
path = resource.path(name=name, namespace=namespace)
119-
return self.request('put', path, body=body, **kwargs)
120-
121-
def patch(self, resource, body=None, name=None, namespace=None, **kwargs):
122-
body = self.serialize_body(body)
123-
name = name or body.get('metadata', {}).get('name')
124-
if not name:
125-
raise ValueError("name is required to patch {}.{}".format(resource.group_version, resource.kind))
126-
if resource.namespaced:
127-
namespace = self.ensure_namespace(resource, namespace, body)
128-
129-
content_type = kwargs.pop('content_type', 'application/strategic-merge-patch+json')
130-
path = resource.path(name=name, namespace=namespace)
131-
132-
return self.request('patch', path, body=body, content_type=content_type, **kwargs)
40+
K8sDynamicClient.__init__(self, client, cache_file=cache_file, discoverer=discoverer)
13341

13442
def apply(self, resource, body=None, name=None, namespace=None):
13543
body = self.serialize_body(body)
@@ -144,139 +52,3 @@ def apply(self, resource, body=None, name=None, namespace=None):
14452
except ApplyException as e:
14553
raise ValueError("Could not apply strategic merge to %s/%s: %s" %
14654
(body['kind'], body['metadata']['name'], e))
147-
148-
def watch(self, resource, namespace=None, name=None, label_selector=None, field_selector=None, resource_version=None, timeout=None):
149-
"""
150-
Stream events for a resource from the Kubernetes API
151-
152-
:param resource: The API resource object that will be used to query the API
153-
:param namespace: The namespace to query
154-
:param name: The name of the resource instance to query
155-
:param label_selector: The label selector with which to filter results
156-
:param field_selector: The field selector with which to filter results
157-
:param resource_version: The version with which to filter results. Only events with
158-
a resource_version greater than this value will be returned
159-
:param timeout: The amount of time in seconds to wait before terminating the stream
160-
161-
:return: Event object with these keys:
162-
'type': The type of event such as "ADDED", "DELETED", etc.
163-
'raw_object': a dict representing the watched object.
164-
'object': A ResourceInstance wrapping raw_object.
165-
166-
Example:
167-
client = DynamicClient(k8s_client)
168-
v1_pods = client.resources.get(api_version='v1', kind='Pod')
169-
170-
for e in v1_pods.watch(resource_version=0, namespace=default, timeout=5):
171-
print(e['type'])
172-
print(e['object'].metadata)
173-
"""
174-
watcher = watch.Watch()
175-
for event in watcher.stream(
176-
resource.get,
177-
namespace=namespace,
178-
name=name,
179-
field_selector=field_selector,
180-
label_selector=label_selector,
181-
resource_version=resource_version,
182-
serialize=False,
183-
timeout_seconds=timeout
184-
):
185-
event['object'] = ResourceInstance(resource, event['object'])
186-
yield event
187-
188-
@meta_request
189-
def request(self, method, path, body=None, **params):
190-
if not path.startswith('/'):
191-
path = '/' + path
192-
193-
path_params = params.get('path_params', {})
194-
query_params = params.get('query_params', [])
195-
if params.get('pretty') is not None:
196-
query_params.append(('pretty', params['pretty']))
197-
if params.get('_continue') is not None:
198-
query_params.append(('continue', params['_continue']))
199-
if params.get('include_uninitialized') is not None:
200-
query_params.append(('includeUninitialized', params['include_uninitialized']))
201-
if params.get('field_selector') is not None:
202-
query_params.append(('fieldSelector', params['field_selector']))
203-
if params.get('label_selector') is not None:
204-
query_params.append(('labelSelector', params['label_selector']))
205-
if params.get('limit') is not None:
206-
query_params.append(('limit', params['limit']))
207-
if params.get('resource_version') is not None:
208-
query_params.append(('resourceVersion', params['resource_version']))
209-
if params.get('timeout_seconds') is not None:
210-
query_params.append(('timeoutSeconds', params['timeout_seconds']))
211-
if params.get('watch') is not None:
212-
query_params.append(('watch', params['watch']))
213-
if params.get('dry_run') is not None:
214-
query_params.append(('dryRun', params['dry_run']))
215-
216-
header_params = params.get('header_params', {})
217-
form_params = []
218-
local_var_files = {}
219-
220-
# Checking Accept header.
221-
new_header_params = dict((key.lower(), value) for key, value in header_params.items())
222-
if not 'accept' in new_header_params:
223-
header_params['Accept'] = self.client.select_header_accept([
224-
'application/json',
225-
'application/yaml',
226-
])
227-
228-
# HTTP header `Content-Type`
229-
if params.get('content_type'):
230-
header_params['Content-Type'] = params['content_type']
231-
else:
232-
header_params['Content-Type'] = self.client.select_header_content_type(['*/*'])
233-
234-
# Authentication setting
235-
auth_settings = ['BearerToken']
236-
237-
return self.client.call_api(
238-
path,
239-
method.upper(),
240-
path_params,
241-
query_params,
242-
header_params,
243-
body=body,
244-
post_params=form_params,
245-
async_req=params.get('async_req'),
246-
files=local_var_files,
247-
auth_settings=auth_settings,
248-
_preload_content=False,
249-
_return_http_data_only=params.get('_return_http_data_only', True)
250-
)
251-
252-
def validate(self, definition, version=None, strict=False):
253-
"""validate checks a kubernetes resource definition
254-
255-
Args:
256-
definition (dict): resource definition
257-
version (str): version of kubernetes to validate against
258-
strict (bool): whether unexpected additional properties should be considered errors
259-
260-
Returns:
261-
warnings (list), errors (list): warnings are missing validations, errors are validation failures
262-
"""
263-
if not HAS_KUBERNETES_VALIDATE:
264-
raise KubernetesValidateMissing()
265-
266-
errors = list()
267-
warnings = list()
268-
try:
269-
if version is None:
270-
try:
271-
version = self.version['kubernetes']['gitVersion']
272-
except KeyError:
273-
version = kubernetes_validate.latest_version()
274-
kubernetes_validate.validate(definition, version, strict)
275-
except kubernetes_validate.utils.ValidationError as e:
276-
errors.append("resource definition validation error at %s: %s" % ('.'.join([str(item) for item in e.path]), e.message)) # noqa: B306
277-
except VersionNotSupportedError:
278-
errors.append("Kubernetes version %s is not supported by kubernetes-validate" % version)
279-
except kubernetes_validate.utils.SchemaNotFoundError as e:
280-
warnings.append("Could not find schema for object kind %s with API version %s in Kubernetes version %s (possibly Custom Resource?)" %
281-
(e.kind, e.api_version, e.version))
282-
return warnings, errors

0 commit comments

Comments
 (0)