Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,23 @@
# v36.0.3

Kubernetes API Version: v1.36.2

### Bug or Regression
- Fix Watch.stream selecting watch instead of follow when streaming pod logs.
- Start the leader election worker thread as a daemon so it does not block process shutdown.
- Fix readline_channel, readline_stdout and readline_stderr crashing with OverflowError when using the default timeout, and returning None when a timeout expires.
- Fix format_quantity returning imprecise, non-canonical values for the milli, micro and nano suffixes, and ignoring quantize=Decimal(0).

# v36.0.2

Kubernetes API Version: v1.36.1

### Uncategorized
- Restored backward compatibility for `Configuration.auth_settings()`:
the legacy `api_key['authorization']` lookup is honored as a fallback
when `api_key['BearerToken']` is not set, fixing 401 Unauthorized
regressions seen after upgrading to v36.0.0 (#2595). (#2604, @GK-07)

# v36.0.1

Kubernetes API Version: v1.36.1
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ supported versions of Kubernetes clusters.
- [client 33.y.z](https://pypi.org/project/kubernetes/33.1.0/): Kubernetes 1.32 or below (+-), Kubernetes 1.33 (✓), Kubernetes 1.34 or above (+-)
- [client 34.y.z](https://pypi.org/project/kubernetes/34.1.0/): Kubernetes 1.33 or below (+-), Kubernetes 1.34 (✓), Kubernetes 1.35 or above (+-)
- [client 35.y.z](https://pypi.org/project/kubernetes/35.0.0/): Kubernetes 1.34 or below (+-), Kubernetes 1.35 (✓), Kubernetes 1.36 or above (+-)
- [client 36.y.z](https://pypi.org/project/kubernetes/36.0.1/): Kubernetes 1.35 or below (+-), Kubernetes 1.36 (✓), Kubernetes 1.37 or above (+-)
- [client 36.y.z](https://pypi.org/project/kubernetes/36.0.3/): Kubernetes 1.35 or below (+-), Kubernetes 1.36 (✓), Kubernetes 1.37 or above (+-)


> See [here](#homogenizing-the-kubernetes-python-client-versions) for an explanation of why there is no v13-v16 release.
Expand Down
54 changes: 54 additions & 0 deletions examples_asyncio/dynamic-client/accept_header.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# Copyright 2021 The Kubernetes Authors.
#
# 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.

"""
This example demonstrates how to pass the custom header in the cluster.

"""

import asyncio

from kubernetes.aio.client import api_client
from kubernetes.aio.client.configuration import Configuration
from kubernetes.aio.config import kube_config
from kubernetes.aio.dynamic import DynamicClient


async def main():
# Creating a dynamic client
config = Configuration()
await kube_config.load_kube_config(client_configuration=config)
async with api_client.ApiClient(configuration=config) as apic:
async with DynamicClient(apic) as client:
# fetching the node api
api = await client.resources.get(api_version="v1", kind="Node")

# Creating a custom header
params = {
"header_params": {
"Accept": "application/json;as=PartialObjectMetadataList;v=v1;g=meta.k8s.io"
}
}

resp = await api.get(**params)

# Printing the kind and apiVersion after passing new header params.
print("VERSION\t\t\t\tKIND")
print(f"{resp.apiVersion}\t\t{resp.kind}")


if __name__ == "__main__":
loop = asyncio.new_event_loop()
loop.run_until_complete(main())
loop.close()
212 changes: 212 additions & 0 deletions examples_asyncio/dynamic-client/cluster_scoped_custom_resource.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
# Copyright 2021 The Kubernetes Authors.
#
# 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.

"""
This example demonstrates the following:
- Creation of a custom resource definition (CRD) using dynamic-client
- Creation of cluster scoped custom resources (CR) using the above created CRD
- List, patch (update), delete the custom resources
- Delete the custom resource definition (CRD)
"""

import asyncio

from kubernetes.aio.client import api_client
from kubernetes.aio.client.configuration import Configuration
from kubernetes.aio.config import kube_config
from kubernetes.aio.dynamic import DynamicClient
from kubernetes.aio.dynamic.exceptions import ResourceNotFoundError


async def main():
# Creating a dynamic client
config = Configuration()
await kube_config.load_kube_config(client_configuration=config)
async with api_client.ApiClient(configuration=config) as apic:
client = await DynamicClient(apic)

# fetching the custom resource definition (CRD) api
crd_api = await client.resources.get(
api_version="apiextensions.k8s.io/v1", kind="CustomResourceDefinition"
)

# Creating a Namespaced CRD named "ingressroutes.apps.example.com"
name = "ingressroutes.apps.example.com"

crd_manifest = {
"apiVersion": "apiextensions.k8s.io/v1",
"kind": "CustomResourceDefinition",
"metadata": {
"name": name,
},
"spec": {
"group": "apps.example.com",
"versions": [
{
"name": "v1",
"schema": {
"openAPIV3Schema": {
"properties": {
"spec": {
"properties": {
"strategy": {"type": "string"},
"virtualhost": {
"properties": {
"fqdn": {"type": "string"},
"tls": {
"properties": {
"secretName": {
"type": "string"
}
},
"type": "object",
},
},
"type": "object",
},
},
"type": "object",
}
},
"type": "object",
}
},
"served": True,
"storage": True,
}
],
"scope": "Cluster",
"names": {
"plural": "ingressroutes",
"listKind": "IngressRouteList",
"singular": "ingressroute",
"kind": "IngressRoute",
"shortNames": ["ir"],
},
},
}

crd_creation_response = await crd_api.create(crd_manifest)
print(
"\n[INFO] custom resource definition `ingressroutes.apps.example.com` created\n"
)
print("SCOPE\t\tNAME")
print(
f"{crd_creation_response.spec.scope}\t\t{crd_creation_response.metadata.name}"
)

# Fetching the "ingressroutes" CRD api

try:
await client.resources.get(
api_version="apps.example.com/v1", kind="IngressRoute"
)
except ResourceNotFoundError:
# Need to wait a sec for the discovery layer to get updated
await asyncio.sleep(2)

ingressroute_api = await client.resources.get(
api_version="apps.example.com/v1", kind="IngressRoute"
)

# Creating a custom resource (CR) `ingress-route-*`, using the above CRD `ingressroutes.apps.example.com`

ingressroute_manifest_first = {
"apiVersion": "apps.example.com/v1",
"kind": "IngressRoute",
"metadata": {
"name": "ingress-route-first",
},
"spec": {
"virtualhost": {
"fqdn": "www.google.com",
"tls": {"secretName": "google-tls"},
},
"strategy": "RoundRobin",
},
}

ingressroute_manifest_second = {
"apiVersion": "apps.example.com/v1",
"kind": "IngressRoute",
"metadata": {
"name": "ingress-route-second",
},
"spec": {
"virtualhost": {
"fqdn": "www.yahoo.com",
"tls": {"secretName": "yahoo-tls"},
},
"strategy": "RoundRobin",
},
}

await ingressroute_api.create(body=ingressroute_manifest_first)
await ingressroute_api.create(body=ingressroute_manifest_second)
print("\n[INFO] custom resources `ingress-route-*` created\n")

# Listing the `ingress-route-*` custom resources

ingress_routes_list = await ingressroute_api.get()
print("NAME\t\t\t\tFQDN\t\tTLS\t\t\t\tSTRATEGY")
for item in ingress_routes_list.items:
print(
f"{item.metadata.name}\t{item.spec.virtualhost.fqdn}\t{item.spec.virtualhost.tls}\t"
f"{item.spec.strategy}"
)

# Patching the ingressroutes custom resources

ingressroute_manifest_first["spec"]["strategy"] = "Random"
ingressroute_manifest_second["spec"]["strategy"] = "WeightedLeastRequest"

await ingressroute_api.patch(
body=ingressroute_manifest_first,
content_type="application/merge-patch+json",
)
await ingressroute_api.patch(
body=ingressroute_manifest_second,
content_type="application/merge-patch+json",
)

print(
"\n[INFO] custom resources `ingress-route-*` patched to update the strategy\n"
)
patched_ingress_routes_list = await ingressroute_api.get()
print("NAME\t\t\t\tFQDN\t\t\tTLS\t\t\tSTRATEGY")
for item in patched_ingress_routes_list.items:
print(
f"{item.metadata.name}\t{item.spec.virtualhost.fqdn}\t{item.spec.virtualhost.tls}\t"
f"{item.spec.strategy}"
)

# Deleting the ingressroutes custom resources

await ingressroute_api.delete(name="ingress-route-first")
await ingressroute_api.delete(name="ingress-route-second")

print("\n[INFO] custom resources `ingress-route-*` deleted")

# Deleting the ingressroutes.apps.example.com custom resource definition

await crd_api.delete(name=name)
print(
"\n[INFO] custom resource definition `ingressroutes.apps.example.com` deleted"
)


if __name__ == "__main__":
loop = asyncio.new_event_loop()
loop.run_until_complete(main())
loop.close()
92 changes: 92 additions & 0 deletions examples_asyncio/dynamic-client/configmap.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# Copyright 2021 The Kubernetes Authors.
#
# 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.

"""
This example demonstrates the following:
- Creation of a k8s configmap using dynamic-client
- List, patch(update), delete the configmap
"""

import asyncio

from kubernetes.aio.client import api_client
from kubernetes.aio.client.configuration import Configuration
from kubernetes.aio.config import kube_config
from kubernetes.aio.dynamic import DynamicClient


async def main():
# Creating a dynamic client
config = Configuration()
await kube_config.load_kube_config(client_configuration=config)
async with api_client.ApiClient(configuration=config) as apic:
client = await DynamicClient(apic)

# fetching the configmap api
api = await client.resources.get(api_version="v1", kind="ConfigMap")

configmap_name = "test-configmap"

configmap_manifest = {
"kind": "ConfigMap",
"apiVersion": "v1",
"metadata": {
"name": configmap_name,
"labels": {
"foo": "bar",
},
},
"data": {
"config.json": '{"command":"/usr/bin/mysqld_safe"}',
"frontend.cnf": "[mysqld]\nbind-address = 10.0.0.3\n",
},
}

# Creating configmap `test-configmap` in the `default` namespace

await api.create(body=configmap_manifest, namespace="default")

print("\n[INFO] configmap `test-configmap` created\n")

# Listing the configmaps in the `default` namespace

configmap_list = await api.get(
name=configmap_name, namespace="default", label_selector="foo=bar"
)

print(f"NAME:\n{configmap_list.metadata.name}\n")
print(f"DATA:\n{configmap_list.data}\n")

# Updating the configmap's data, `config.json`

configmap_manifest["data"]["config.json"] = "{}"

configmap_patched = await api.patch(
name=configmap_name, namespace="default", body=configmap_manifest
)

print("\n[INFO] configmap `test-configmap` patched\n")
print(f"NAME:\n{configmap_patched.metadata.name}\n")
print(f"DATA:\n{configmap_patched.data}\n")

# Deleting configmap `test-configmap` from the `default` namespace

await api.delete(name=configmap_name, body={}, namespace="default")
print("\n[INFO] configmap `test-configmap` deleted\n")


if __name__ == "__main__":
loop = asyncio.new_event_loop()
loop.run_until_complete(main())
loop.close()
Loading