-
Notifications
You must be signed in to change notification settings - Fork 184
Dynamic Client #56
Dynamic Client #56
Conversation
Codecov Report
@@ Coverage Diff @@
## master #56 +/- ##
=======================================
Coverage 91.85% 91.85%
=======================================
Files 11 11
Lines 994 994
=======================================
Hits 913 913
Misses 81 81 Continue to review full report at Codecov.
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the contribution! I left some comments :)
dynamic/client.py
Outdated
from kubernetes.client.api_client import ApiClient | ||
from kubernetes.client.rest import ApiException | ||
|
||
from openshift.dynamic.exceptions import ResourceNotFoundError, ResourceNotUniqueError, api_exception |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The module path needs to be changed
dynamic/client.py
Outdated
def group_version(self): | ||
if self.group: | ||
return '{}/{}'.format(self.group, self.api_version) | ||
return self.api_version |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: '/'.join(filter(None, [self.group, self.api_version])) like Line112?
dynamic/client.py
Outdated
kind: The kind of the resource | ||
arbitrary arguments (see below), in random order | ||
|
||
The arbitrary arguments can be any valid attribute for an openshift.dynamic.Resource object |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ditto: update the module path
dynamic/client.py
Outdated
def path(self, name=None, namespace=None): | ||
url_type = [] | ||
path_params = {} | ||
if self.namespaced and namespace: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should we catch the case where namespaced
is set but namespace
is None
?
or does ensure_namespace
rule out that case already?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if namespaced
is set and namespace
is None
, then we can still construct a valid path (api/v1/pods
for example)
dynamic/client.py
Outdated
'base': '/{}/{}'.format(full_prefix, self.name), | ||
'namespaced_base': '/{}/namespaces/{{namespace}}/{}'.format(full_prefix, self.name), | ||
'full': '/{}/{}/{{name}}'.format(full_prefix, self.name), | ||
'namespaced_full': '/{}/namespaces/{{namespace}}/{}/{{name}}'.format(full_prefix, self.name) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can we determine if the resource is cluster-scoped or namespaced using self.namespaced
here and only return two urls base
and full
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you can access namespaced resources without providing a namespace (ie, list all pods in every namespace, etc), so I don't think we should
dynamic/client.py
Outdated
content_type = self.client.\ | ||
select_header_content_type(['application/json-patch+json', 'application/merge-patch+json', 'application/strategic-merge-patch+json']) | ||
|
||
return self.request('patch', path, body=body, content_type=content_type) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
pass **kwargs
to self.request
@meta_request | ||
def delete(self, resource, name=None, namespace=None, label_selector=None, field_selector=None, **kwargs): | ||
if not (name or label_selector or field_selector): | ||
raise ValueError("At least one of name|label_selector|field_selector is required") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From the design doc, the following actions are marked as TODO: update
, watch
, deletecollection
, proxy
. I see update
has been implemented (replace
). I'm just curious if we don't want to have deletecollection
for the first iteration.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, the only reason I didn't yet implement deletecollection is that I'm not familiar with it, would love to get it in
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
still TODO
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's fine to add deletecollection
as a followup
dynamic/client.py
Outdated
full_prefix = '{}/{}'.format(self.prefix, self.group_version) | ||
return { | ||
'full': '/{}/{}/{{name}}/{}'.format(full_prefix, self.parent.name, self.subresource), | ||
'namespaced_full': '/{}/namespaces/{{namespace}}/{}/{{name}}/{}'.format(full_prefix, self.parent.name, self.subresource) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ditto: if we could check self.namespaced
and combine the two urls into one
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
see above response
dynamic/client.py
Outdated
def _load_server_info(self): | ||
self.__version = {'kubernetes': load_json(self.request('get', '/version'))} | ||
try: | ||
self.__version['openshift'] = load_json(self.request('get', '/version/openshift')) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can go ahead and take it out, this belongs in openshift specific code not upstream
@roycaihw rebased to keep up to date with branch, any additional comments? |
ping @roycaihw |
We've also had some community interest in the other discussed dynamic implementation (the one that uses the OpenAPI spec for discovery), I haven't had time to look into it again yet but I'm hoping I'll get a chance for a second pass soon. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks good to me in general. I left one nit and one question for clarification.
dynamic/client.py
Outdated
return self.request('post', path, body=body, **kwargs) | ||
|
||
@meta_request | ||
def delete(self, resource, name=None, namespace=None, label_selector=None, field_selector=None, **kwargs): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
k8s delete requests can have body of type DeleteOptions
as alternative of some query parameters, although it's not a good practice to send delete request with body I suppose.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good call, this should definitely support delete options.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
still TODO
dynamic/client.py
Outdated
self.client = client | ||
self.configuration = client.configuration | ||
self._load_server_info() | ||
self.__resources = ResourceContainer(self.parse_api_groups()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IIUC people need to create another ResourceContainer
with client.parse_api_groups()
when they want to re-discover new CRDs and aggregated APIs - self.__resources
only records the resources that exist when the dynamic client gets created - is that correct?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're right, I'll break this stuff out into a discovery()
method so that it can be called repeatedly
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll break this stuff out into a discovery() method so that it can be called repeatedly
This can be done in a separate PR.
I think this PR will be good to go after @fabianvf adds This dynamic client should also be e2e tested (something like test_client.py), probably in another PR. |
from functools import partial | ||
import yaml | ||
from pprint import pformat | ||
from kubernetes.client.rest import ApiException |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
better to have the import above be ordered?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah I can clean that up
503: ServiceUnavailableError, | ||
504: ServerTimeoutError, | ||
}.get(e.status, DynamicApiError)(e, tb) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if just want to add traceback, why do you need to add all these specific HTTP errors (BadRequestError, UnauthorizedError, ...)? Is DynamicApiError alone not enough for the purpose?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I prefer to expose errors that correspond to the HTTP errors we receive, it provides a cleaner way to handle errors when consuming the client (since you can just catch a NotFoundError rather than catching a DynamicApiError and checking the status code). Catching a generic DynamicApiError and checking the code still works if the user prefers to handle it that way.
dynamic/client.py
Outdated
header_params['Accept'] = self.client.select_header_accept([ | ||
'application/json', | ||
'application/yaml', | ||
'application/vnd.kubernetes.protobuf' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i don't think protobuf is supported in python client, better to remove it here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ack, good call
sorry, I've been on PTO + planning meetings the last few weeks, I'll try and carve up some time to clean all this up sometime this week or next |
We're actually doing a little bit of a rework of the dynamic client in openshift, driven by user feedback. I think I might hold off a bit on updating this PR, so that I can bring those improvements in |
@fabianvf I see that you're working on a dynamic client for OpenShift. Are there any plans to backport the dynamic client to this library? |
Yes, there's actually almost nothing specific to openshift in the client. There was a lot of churn so I put this PR on hold while we stabilized a bit, now it's mostly a matter of finding the time to bring in the relevant new stuff. |
Issues go stale after 90d of inactivity. If this issue is safe to close now please do so with Send feedback to sig-testing, kubernetes/test-infra and/or fejta. |
/remove-lifecycle stale
|
/lgtm Thanks a lot for working on this! Will merge after the test failure gets resolved kubernetes-client/python#919 |
[APPROVALNOTIFIER] This PR is APPROVED This pull-request has been approved by: fabianvf, roycaihw The full list of commands accepted by this bot can be found here. The pull request process is described here
Needs approval from an approver in each of these files:
Approvers can indicate their approval by writing |
i have a meta request, could you please squash the commits to a few logical commits? |
61d676c
to
53c4cb2
Compare
/lgtm |
Still TODO: