Skip to content

Commit 4695715

Browse files
authored
Add Dynamic Client support (#260)
* Add dynamic client support * Add dynamic client support * refactor(dynamic): use await magic method * Add docs readme info for DynamicClient Signed-off-by: Bob Haddleton <[email protected]> * Fix example Signed-off-by: Bob Haddleton <[email protected]> * Fix examples Signed-off-by: Bob Haddleton <[email protected]> * Remove requirements entry and fix example Signed-off-by: Bob Haddleton <[email protected]> * [docs] add readme info for DynamicClient Signed-off-by: Bob Haddleton <[email protected]> * [fix] cleanup examples Signed-off-by: Bob Haddleton <[email protected]> * [refactor] review feedback changes Signed-off-by: Bob Haddleton <[email protected]> * [refactor] remove changes from client/rest.py Signed-off-by: Bob Haddleton <[email protected]> * [test] Fix e2e test case Signed-off-by: Bob Haddleton <[email protected]> * [lint] Fix flake8 errors Signed-off-by: Bob Haddleton <[email protected]> * [lint] Fix isort errors Signed-off-by: Bob Haddleton <[email protected]> --------- Signed-off-by: Bob Haddleton <[email protected]>
1 parent b291a3a commit 4695715

19 files changed

+3148
-1
lines changed

doc/source/readme.rst

+34
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,40 @@ To list all pods:
5858
More complicated examples, like asynchronous multiple watch or tail logs from pods,
5959
you can find in `examples/` folder.
6060

61+
There is also support for DynamicClient which will query the cluster for all supported
62+
resources. This allows for dynamic resource selection at runtime.
63+
The above example using DynamicClient would look like this:
64+
::
65+
66+
import asyncio
67+
from kubernetes_asyncio import config
68+
from kubernetes_asyncio.client.api_client import ApiClient
69+
from kubernetes_asyncio.dynamic import DynamicClient
70+
71+
72+
async def main():
73+
# Configs can be set in Configuration class directly or using helper
74+
# utility. If no argument provided, the config will be loaded from
75+
# default location.
76+
await config.load_kube_config()
77+
78+
# use the context manager to close http sessions automatically
79+
async with ApiClient() as api:
80+
client = await DynamicClient(api)
81+
v1 = await client.resources.get(api_version="v1", kind="Pod")
82+
print("Listing pods with their IPs:")
83+
ret = await v1.get()
84+
85+
for i in ret.items:
86+
print(i.status.pod_ip, i.metadata.namespace, i.metadata.name)
87+
88+
89+
if __name__ == '__main__':
90+
loop = asyncio.new_event_loop()
91+
loop.run_until_complete(main())
92+
loop.close()
93+
94+
Additional examples are in the `examples/dynamic-client` folder.
6195

6296
Versions
6397
--------
+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# Copyright 2021 The Kubernetes Authors.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
"""
16+
This example demonstrates how to pass the custom header in the cluster.
17+
18+
"""
19+
20+
import asyncio
21+
22+
from kubernetes_asyncio.client import api_client
23+
from kubernetes_asyncio.client.configuration import Configuration
24+
from kubernetes_asyncio.config import kube_config
25+
from kubernetes_asyncio.dynamic import DynamicClient
26+
27+
28+
async def main():
29+
# Creating a dynamic client
30+
config = Configuration()
31+
await kube_config.load_kube_config(client_configuration=config)
32+
async with api_client.ApiClient(configuration=config) as apic:
33+
async with DynamicClient(apic) as client:
34+
# fetching the node api
35+
api = await client.resources.get(api_version="v1", kind="Node")
36+
37+
# Creating a custom header
38+
params = {'header_params': {'Accept': 'application/json;as=PartialObjectMetadataList;v=v1;g=meta.k8s.io'}}
39+
40+
resp = await api.get(**params)
41+
42+
# Printing the kind and apiVersion after passing new header params.
43+
print("VERSION\t\t\t\tKIND")
44+
print(f"{resp.apiVersion}\t\t{resp.kind}")
45+
46+
47+
if __name__ == "__main__":
48+
loop = asyncio.new_event_loop()
49+
loop.run_until_complete(main())
50+
loop.close()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
# Copyright 2021 The Kubernetes Authors.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
"""
16+
This example demonstrates the following:
17+
- Creation of a custom resource definition (CRD) using dynamic-client
18+
- Creation of cluster scoped custom resources (CR) using the above created CRD
19+
- List, patch (update), delete the custom resources
20+
- Delete the custom resource definition (CRD)
21+
"""
22+
23+
import asyncio
24+
25+
from kubernetes_asyncio.client import api_client
26+
from kubernetes_asyncio.client.configuration import Configuration
27+
from kubernetes_asyncio.config import kube_config
28+
from kubernetes_asyncio.dynamic import DynamicClient
29+
from kubernetes_asyncio.dynamic.exceptions import ResourceNotFoundError
30+
31+
32+
async def main():
33+
# Creating a dynamic client
34+
config = Configuration()
35+
await kube_config.load_kube_config(client_configuration=config)
36+
async with api_client.ApiClient(configuration=config) as apic:
37+
client = await DynamicClient(apic)
38+
39+
# fetching the custom resource definition (CRD) api
40+
crd_api = await client.resources.get(
41+
api_version="apiextensions.k8s.io/v1", kind="CustomResourceDefinition"
42+
)
43+
44+
# Creating a Namespaced CRD named "ingressroutes.apps.example.com"
45+
name = "ingressroutes.apps.example.com"
46+
47+
crd_manifest = {
48+
"apiVersion": "apiextensions.k8s.io/v1",
49+
"kind": "CustomResourceDefinition",
50+
"metadata": {
51+
"name": name,
52+
},
53+
"spec": {
54+
"group": "apps.example.com",
55+
"versions": [
56+
{
57+
"name": "v1",
58+
"schema": {
59+
"openAPIV3Schema": {
60+
"properties": {
61+
"spec": {
62+
"properties": {
63+
"strategy": {"type": "string"},
64+
"virtualhost": {
65+
"properties": {
66+
"fqdn": {"type": "string"},
67+
"tls": {
68+
"properties": {
69+
"secretName": {"type": "string"}
70+
},
71+
"type": "object",
72+
},
73+
},
74+
"type": "object",
75+
},
76+
},
77+
"type": "object",
78+
}
79+
},
80+
"type": "object",
81+
}
82+
},
83+
"served": True,
84+
"storage": True,
85+
}
86+
],
87+
"scope": "Cluster",
88+
"names": {
89+
"plural": "ingressroutes",
90+
"listKind": "IngressRouteList",
91+
"singular": "ingressroute",
92+
"kind": "IngressRoute",
93+
"shortNames": ["ir"],
94+
},
95+
},
96+
}
97+
98+
crd_creation_response = await crd_api.create(crd_manifest)
99+
print(
100+
"\n[INFO] custom resource definition `ingressroutes.apps.example.com` created\n"
101+
)
102+
print("SCOPE\t\tNAME")
103+
print(f"{crd_creation_response.spec.scope}\t\t{crd_creation_response.metadata.name}")
104+
105+
# Fetching the "ingressroutes" CRD api
106+
107+
try:
108+
await client.resources.get(
109+
api_version="apps.example.com/v1", kind="IngressRoute"
110+
)
111+
except ResourceNotFoundError:
112+
# Need to wait a sec for the discovery layer to get updated
113+
await asyncio.sleep(2)
114+
115+
ingressroute_api = await client.resources.get(
116+
api_version="apps.example.com/v1", kind="IngressRoute"
117+
)
118+
119+
# Creating a custom resource (CR) `ingress-route-*`, using the above CRD `ingressroutes.apps.example.com`
120+
121+
ingressroute_manifest_first = {
122+
"apiVersion": "apps.example.com/v1",
123+
"kind": "IngressRoute",
124+
"metadata": {
125+
"name": "ingress-route-first",
126+
},
127+
"spec": {
128+
"virtualhost": {
129+
"fqdn": "www.google.com",
130+
"tls": {"secretName": "google-tls"},
131+
},
132+
"strategy": "RoundRobin",
133+
},
134+
}
135+
136+
ingressroute_manifest_second = {
137+
"apiVersion": "apps.example.com/v1",
138+
"kind": "IngressRoute",
139+
"metadata": {
140+
"name": "ingress-route-second",
141+
},
142+
"spec": {
143+
"virtualhost": {
144+
"fqdn": "www.yahoo.com",
145+
"tls": {"secretName": "yahoo-tls"},
146+
},
147+
"strategy": "RoundRobin",
148+
},
149+
}
150+
151+
await ingressroute_api.create(body=ingressroute_manifest_first)
152+
await ingressroute_api.create(body=ingressroute_manifest_second)
153+
print("\n[INFO] custom resources `ingress-route-*` created\n")
154+
155+
# Listing the `ingress-route-*` custom resources
156+
157+
ingress_routes_list = await ingressroute_api.get()
158+
print("NAME\t\t\t\tFQDN\t\tTLS\t\t\t\tSTRATEGY")
159+
for item in ingress_routes_list.items:
160+
print(f"{item.metadata.name}\t{item.spec.virtualhost.fqdn}\t{item.spec.virtualhost.tls}\t"
161+
f"{item.spec.strategy}")
162+
163+
# Patching the ingressroutes custom resources
164+
165+
ingressroute_manifest_first["spec"]["strategy"] = "Random"
166+
ingressroute_manifest_second["spec"]["strategy"] = "WeightedLeastRequest"
167+
168+
await ingressroute_api.patch(body=ingressroute_manifest_first, content_type="application/merge-patch+json")
169+
await ingressroute_api.patch(body=ingressroute_manifest_second, content_type="application/merge-patch+json")
170+
171+
print(
172+
"\n[INFO] custom resources `ingress-route-*` patched to update the strategy\n"
173+
)
174+
patched_ingress_routes_list = await ingressroute_api.get()
175+
print("NAME\t\t\t\tFQDN\t\t\tTLS\t\t\tSTRATEGY")
176+
for item in patched_ingress_routes_list.items:
177+
print(f"{item.metadata.name}\t{item.spec.virtualhost.fqdn}\t{item.spec.virtualhost.tls}\t"
178+
f"{item.spec.strategy}")
179+
180+
# Deleting the ingressroutes custom resources
181+
182+
await ingressroute_api.delete(name="ingress-route-first")
183+
await ingressroute_api.delete(name="ingress-route-second")
184+
185+
print("\n[INFO] custom resources `ingress-route-*` deleted")
186+
187+
# Deleting the ingressroutes.apps.example.com custom resource definition
188+
189+
await crd_api.delete(name=name)
190+
print(
191+
"\n[INFO] custom resource definition `ingressroutes.apps.example.com` deleted"
192+
)
193+
194+
195+
if __name__ == "__main__":
196+
loop = asyncio.new_event_loop()
197+
loop.run_until_complete(main())
198+
loop.close()

examples/dynamic-client/configmap.py

+92
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
# Copyright 2021 The Kubernetes Authors.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
"""
16+
This example demonstrates the following:
17+
- Creation of a k8s configmap using dynamic-client
18+
- List, patch(update), delete the configmap
19+
"""
20+
21+
import asyncio
22+
23+
from kubernetes_asyncio.client import api_client
24+
from kubernetes_asyncio.client.configuration import Configuration
25+
from kubernetes_asyncio.config import kube_config
26+
from kubernetes_asyncio.dynamic import DynamicClient
27+
28+
29+
async def main():
30+
# Creating a dynamic client
31+
config = Configuration()
32+
await kube_config.load_kube_config(client_configuration=config)
33+
async with api_client.ApiClient(configuration=config) as apic:
34+
client = await DynamicClient(apic)
35+
36+
# fetching the configmap api
37+
api = await client.resources.get(api_version="v1", kind="ConfigMap")
38+
39+
configmap_name = "test-configmap"
40+
41+
configmap_manifest = {
42+
"kind": "ConfigMap",
43+
"apiVersion": "v1",
44+
"metadata": {
45+
"name": configmap_name,
46+
"labels": {
47+
"foo": "bar",
48+
},
49+
},
50+
"data": {
51+
"config.json": '{"command":"/usr/bin/mysqld_safe"}',
52+
"frontend.cnf": "[mysqld]\nbind-address = 10.0.0.3\n",
53+
},
54+
}
55+
56+
# Creating configmap `test-configmap` in the `default` namespace
57+
58+
await api.create(body=configmap_manifest, namespace="default")
59+
60+
print("\n[INFO] configmap `test-configmap` created\n")
61+
62+
# Listing the configmaps in the `default` namespace
63+
64+
configmap_list = await api.get(
65+
name=configmap_name, namespace="default", label_selector="foo=bar"
66+
)
67+
68+
print(f"NAME:\n{configmap_list.metadata.name}\n")
69+
print(f"DATA:\n{configmap_list.data}\n")
70+
71+
# Updating the configmap's data, `config.json`
72+
73+
configmap_manifest["data"]["config.json"] = "{}"
74+
75+
configmap_patched = await api.patch(
76+
name=configmap_name, namespace="default", body=configmap_manifest
77+
)
78+
79+
print("\n[INFO] configmap `test-configmap` patched\n")
80+
print(f"NAME:\n{configmap_patched.metadata.name}\n")
81+
print(f"DATA:\n{configmap_patched.data}\n")
82+
83+
# Deleting configmap `test-configmap` from the `default` namespace
84+
85+
await api.delete(name=configmap_name, body={}, namespace="default")
86+
print("\n[INFO] configmap `test-configmap` deleted\n")
87+
88+
89+
if __name__ == "__main__":
90+
loop = asyncio.new_event_loop()
91+
loop.run_until_complete(main())
92+
loop.close()

0 commit comments

Comments
 (0)