diff --git a/CHANGELOG.md b/CHANGELOG.md index 12b1d6d464..bf79a93014 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,17 @@ +# v17.0.0-snapshot + +Kubernetes API Version: 1.17.17 + +Changelog since v17.17.0b1: + +### Bug or Regression +- Fix watch stream non-chunked response handling ([kubernetes-client/python-base#231](https://github.com/kubernetes-client/python-base/pull/231), [@dhague](https://github.com/dhague)) +- Fixed a decoding error for BOOTMARK watch events ([kubernetes-client/python-base#234](https://github.com/kubernetes-client/python-base/pull/234), [@yliaog](https://github.com/yliaog)) + +### Feature +- Load_kube_config_from_dict() support define custom temp files path ([kubernetes-client/python-base#233](https://github.com/kubernetes-client/python-base/pull/233), [@onecer](https://github.com/onecer)) +- The dynamic client now supports customizing http "Accept" header through the `header_params` parameter, which can be used to customizing API server response, e.g. retrieving object metadata only. ([kubernetes-client/python-base#236](https://github.com/kubernetes-client/python-base/pull/236), [@Yashks1994](https://github.com/Yashks1994)) + # v17.17.0b1 Kubernetes API Version: 1.17.17 @@ -16,6 +30,55 @@ Changelog since v17.14.0a1: - raise exception when an empty config file is passed to load_kube_config [kubernetes-client/python-base#223](https://github.com/kubernetes-client/python-base/pull/223) - Fix bug with Watch and 410 retries [kubernetes-client/python-base#227](https://github.com/kubernetes-client/python-base/pull/227) +# v18.17.0a1 + +Kubernetes API Version: 1.18.17 + +**Important Information:** + +- The Kubernetes Python client versioning scheme has changed. The version numbers used till Kubernetes Python Client v12.y.z lagged behind the actual Kubernetes minor version numbers. From this release, the client is moving a version format `vY.Z.P` where `Y` and `Z` are respectively from the Kubernetes version `v1.Y.Z` and `P` would incremented due to changes on the Python client side itself. Ref: https://github.com/kubernetes-client/python/issues/1244 +- Python 2 had reached [End of Life](https://www.python.org/doc/sunset-python-2/) on January 1, 2020. The Kubernetes Python Client has dropped support for Python 2 from this release (v18.0.0) and will no longer provide support to older clients as per the [Kubernetes support policy](https://kubernetes.io/docs/setup/release/version-skew-policy/#supported-versions). + +**Deprecations:** +- The following deprecated APIs can no longer be served: + - All resources under `apps/v1beta1` and `apps/v1beta2` - use `apps/v1` instead + - `daemonsets`, `deployments`, `replicasets` resources under `extensions/v1beta1` - use `apps/v1` instead + - `networkpolicies` resources under `extensions/v1beta1` - use `networking.k8s.io/v1` instead + - `podsecuritypolicies` resources under `extensions/v1beta1` - use `policy/v1beta1` instead ([#85903](https://github.com/kubernetes/kubernetes/pull/85903), [@liggitt](https://github.com/liggitt)) [SIG API Machinery, Apps, Cluster Lifecycle, Instrumentation and Testing] + +**New Feature:** +- Support leader election. [kubernetes-client/python-base#206](https://github.com/kubernetes-client/python-base/pull/206) + +**Bug Fix:** +- Raise exception when an empty config file is passed to load_kube_config. [kubernetes-client/python-base#223](https://github.com/kubernetes-client/python-base/pull/223) +- fix: load cache error when CacheDecoder object is not callable. [kubernetes-client/python-base#226](https://github.com/kubernetes-client/python-base/pull/226) +- Fix Watch retries with 410 errors. [kubernetes-client/python-base#227](https://github.com/kubernetes-client/python-base/pull/227) +- Automatically handles chunked or non-chunked responses. Fix ResponseNotChunked error from watch. [kubernetes-client/python-base#231](https://github.com/kubernetes-client/python-base/pull/231) + +**API Change:** +- Add allowWatchBookmarks, resoureVersionMatch parameters to custom objects. [kubernetes-client/gen#180](https://github.com/kubernetes-client/gen/pull/180) +- Fix bug in reflector that couldn't recover from "Too large resource version" errors ([#92537](https://github.com/kubernetes/kubernetes/pull/92537), [@wojtek-t](https://github.com/wojtek-t)) [SIG API Machinery] +- Fixed: log timestamps now include trailing zeros to maintain a fixed width ([#91207](https://github.com/kubernetes/kubernetes/pull/91207), [@iamchuckss](https://github.com/iamchuckss)) [SIG Apps and Node] +- Fixed: log timestamps now include trailing zeros to maintain a fixed width ([#91207](https://github.com/kubernetes/kubernetes/pull/91207), [@iamchuckss](https://github.com/iamchuckss)) [SIG Apps and Node] +- Resolve regression in metadata.managedFields handling in update/patch requests submitted by older API clients ([#92007](https://github.com/kubernetes/kubernetes/pull/92007), [@apelisse](https://github.com/apelisse)) [SIG API Machinery and Testing] +- A new IngressClass resource has been added to enable better Ingress configuration. ([#88509](https://github.com/kubernetes/kubernetes/pull/88509), [@robscott](https://github.com/robscott)) [SIG API Machinery, Apps, CLI, Network, Node and Testing] +- The CSIDriver API has graduated to storage.k8s.io/v1, and is now available for use. ([#84814](https://github.com/kubernetes/kubernetes/pull/84814), [@huffmanca](https://github.com/huffmanca)) [SIG Storage] +- autoscaling/v2beta2 HorizontalPodAutoscaler added a `spec.behavior` field that allows scale behavior to be configured. Behaviors are specified separately for scaling up and down. In each direction a stabilization window can be specified as well as a list of policies and how to select amongst them. Policies can limit the absolute number of pods added or removed, or the percentage of pods added or removed. ([#74525](https://github.com/kubernetes/kubernetes/pull/74525), [@gliush](https://github.com/gliush)) [SIG API Machinery, Apps, Autoscaling and CLI] +- Ingress: + - `spec.ingressClassName` replaces the deprecated `kubernetes.io/ingress.class` annotation, and allows associating an Ingress object with a particular controller. + - path definitions added a `pathType` field to allow indicating how the specified path should be matched against incoming requests. Valid values are `Exact`, `Prefix`, and `ImplementationSpecific` ([#88587](https://github.com/kubernetes/kubernetes/pull/88587), [@cmluciano](https://github.com/cmluciano)) [SIG Apps, Cluster Lifecycle and Network] +- The alpha feature `AnyVolumeDataSource` enables PersistentVolumeClaim objects to use the spec.dataSource field to reference a custom type as a data source ([#88636](https://github.com/kubernetes/kubernetes/pull/88636), [@bswartz](https://github.com/bswartz)) [SIG Apps and Storage] +- The alpha feature `ConfigurableFSGroupPolicy` enables v1 Pods to specify a spec.securityContext.fsGroupChangePolicy policy to control how file permissions are applied to volumes mounted into the pod. ([#88488](https://github.com/kubernetes/kubernetes/pull/88488), [@gnufied](https://github.com/gnufied)) [SIG Storage] +- The alpha feature `ServiceAppProtocol` enables setting an `appProtocol` field in ServicePort and EndpointPort definitions. ([#88503](https://github.com/kubernetes/kubernetes/pull/88503), [@robscott](https://github.com/robscott)) [SIG Apps and Network] +- The alpha feature `ImmutableEphemeralVolumes` enables an `immutable` field in both Secret and ConfigMap objects to mark their contents as immutable. ([#86377](https://github.com/kubernetes/kubernetes/pull/86377), [@wojtek-t](https://github.com/wojtek-t)) [SIG Apps, CLI and Testing] +- The beta feature `ServerSideApply` enables tracking and managing changed fields for all new objects, which means there will be `managedFields` in `metadata` with the list of managers and their owned fields. +- The alpha feature `ServiceAccountIssuerDiscovery` enables publishing OIDC discovery information and service account token verification keys at `/.well-known/openid-configuration` and `/openid/v1/jwks` endpoints by API servers configured to issue service account tokens. ([#80724](https://github.com/kubernetes/kubernetes/pull/80724), [@cceckman](https://github.com/cceckman)) [SIG API Machinery, Auth, Cluster Lifecycle and Testing] +- CustomResourceDefinition schemas that use `x-kubernetes-list-map-keys` to specify properties that uniquely identify list items must make those properties required or have a default value, to ensure those properties are present for all list items. See https://kubernetes.io/docs/reference/using-api/api-concepts/#merge-strategy for details. ([#88076](https://github.com/kubernetes/kubernetes/pull/88076), [@eloyekunle](https://github.com/eloyekunle)) [SIG API Machinery and Testing] +- CustomResourceDefinition schemas that use `x-kubernetes-list-type: map` or `x-kubernetes-list-type: set` now enable validation that the list items in the corresponding custom resources are unique. ([#84920](https://github.com/kubernetes/kubernetes/pull/84920), [@sttts](https://github.com/sttts)) [SIG API Machinery] + + +To read the full CHANGELOG visit [here](https://raw.githubusercontent.com/kubernetes/kubernetes/master/CHANGELOG/CHANGELOG-1.18.md). + # v17.14.0a1 Kubernetes API Version: 1.17.14 diff --git a/README.md b/README.md index 6b4fcfe7d3..f91edc0a23 100644 --- a/README.md +++ b/README.md @@ -87,6 +87,7 @@ supported versions of Kubernetes clusters. - [client 11.y.z](https://pypi.org/project/kubernetes/11.0.0/): Kubernetes 1.14 or below (+-), Kubernetes 1.15 (✓), Kubernetes 1.16 or above (+-) - [client 12.y.z](https://pypi.org/project/kubernetes/12.0.1/): Kubernetes 1.15 or below (+-), Kubernetes 1.16 (✓), Kubernetes 1.17 or above (+-) - [client 17.y.z](https://pypi.org/project/kubernetes/17.14.0a1/): Kubernetes 1.16 or below (+-), Kubernetes 1.17 (✓), Kubernetes 1.18 or above (+-) +- [client 18.y.z](https://pypi.org/project/kubernetes/18.17.0a1/): Kubernetes 1.17 or below (+-), Kubernetes 1.18 (✓), Kubernetes 1.19 or above (+-) > See [here](#homogenizing-the-kubernetes-python-client-versions) for an explaination of why there is no v13-v16 release. @@ -124,6 +125,7 @@ between client-python versions. | 12.0 Alpha/Beta | Kubernetes main repo, 1.16 branch | ✗ | | 12.0 | Kubernetes main repo, 1.16 branch | ✓ | | 17.0 Alpha/Beta | Kubernetes main repo, 1.17 branch | ✓ | +| 18.0 Alpha/Beta | Kubernetes main repo, 1.18 branch | ✓ | > See [here](#homogenizing-the-kubernetes-python-client-versions) for an explaination of why there is no v13-v16 release. diff --git a/examples/dynamic-client/cluster_scoped_custom_resource.py b/examples/dynamic-client/cluster_scoped_custom_resource.py new file mode 100644 index 0000000000..532a763619 --- /dev/null +++ b/examples/dynamic-client/cluster_scoped_custom_resource.py @@ -0,0 +1,213 @@ +# 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 defintion (CRD) +""" + +from kubernetes import config, dynamic +from kubernetes.dynamic.exceptions import ResourceNotFoundError +from kubernetes.client import api_client +import time + + +def main(): + # Creating a dynamic client + client = dynamic.DynamicClient( + api_client.ApiClient(configuration=config.load_kube_config()) + ) + + # fetching the custom resource definition (CRD) api + crd_api = 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 = crd_api.create(crd_manifest) + print( + "\n[INFO] custom resource definition `ingressroutes.apps.example.com` created\n" + ) + print("%s\t\t%s" % ("SCOPE", "NAME")) + print( + "%s\t\t%s\n" + % (crd_creation_response.spec.scope, crd_creation_response.metadata.name) + ) + + # Fetching the "ingressroutes" CRD api + + try: + ingressroute_api = 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 + time.sleep(2) + + ingressroute_api = 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", + }, + } + + ingressroute_api.create(body=ingressroute_manifest_first) + 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 = ingressroute_api.get() + print("%s\t\t\t%s\t\t%s\t\t\t\t%s" % ("NAME", "FQDN", "TLS", "STRATEGY")) + for item in ingress_routes_list.items: + print( + "%s\t%s\t%s\t%s" + % ( + item.metadata.name, + item.spec.virtualhost.fqdn, + item.spec.virtualhost.tls, + item.spec.strategy, + ) + ) + + # Patching the ingressroutes custom resources + + ingressroute_manifest_first["spec"]["strategy"] = "Random" + ingressroute_manifest_second["spec"]["strategy"] = "WeightedLeastRequest" + + patch_ingressroute_first = ingressroute_api.patch( + body=ingressroute_manifest_first, content_type="application/merge-patch+json" + ) + patch_ingressroute_second = 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 = ingressroute_api.get() + print("%s\t\t\t%s\t\t%s\t\t\t\t%s" % ("NAME", "FQDN", "TLS", "STRATEGY")) + for item in patched_ingress_routes_list.items: + print( + "%s\t%s\t%s\t%s" + % ( + item.metadata.name, + item.spec.virtualhost.fqdn, + item.spec.virtualhost.tls, + item.spec.strategy, + ) + ) + + # Deleting the ingressroutes custom resources + + delete_ingressroute_first = ingressroute_api.delete(name="ingress-route-first") + delete_ingressroute_second = ingressroute_api.delete(name="ingress-route-second") + + print("\n[INFO] custom resources `ingress-route-*` deleted") + + # Deleting the ingressroutes.apps.example.com custom resource definition + + crd_api.delete(name=name) + print( + "\n[INFO] custom resource definition `ingressroutes.apps.example.com` deleted" + ) + + +if __name__ == "__main__": + main() diff --git a/examples/dynamic-client/configmap.py b/examples/dynamic-client/configmap.py new file mode 100644 index 0000000000..15094df6be --- /dev/null +++ b/examples/dynamic-client/configmap.py @@ -0,0 +1,85 @@ +# 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 +""" + +from kubernetes import config, dynamic +from kubernetes.client import api_client + + +def main(): + # Creating a dynamic client + client = dynamic.DynamicClient( + api_client.ApiClient(configuration=config.load_kube_config()) + ) + + # fetching the configmap api + api = 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 + + configmap = api.create(body=configmap_manifest, namespace="default") + + print("\n[INFO] configmap `test-configmap` created\n") + + # Listing the configmaps in the `default` namespace + + configmap_list = api.get( + name=configmap_name, namespace="default", label_selector="foo=bar" + ) + + print("NAME:\n%s\n" % (configmap_list.metadata.name)) + print("DATA:\n%s\n" % (configmap_list.data)) + + # Updating the configmap's data, `config.json` + + configmap_manifest["data"]["config.json"] = "{}" + + configmap_patched = api.patch( + name=configmap_name, namespace="default", body=configmap_manifest + ) + + print("\n[INFO] configmap `test-configmap` patched\n") + print("NAME:\n%s\n" % (configmap_patched.metadata.name)) + print("DATA:\n%s\n" % (configmap_patched.data)) + + # Deleting configmap `test-configmap` from the `default` namespace + + configmap_deleted = api.delete(name=configmap_name, body={}, namespace="default") + print("\n[INFO] configmap `test-configmap` deleted\n") + + +if __name__ == "__main__": + main() diff --git a/examples/dynamic-client/deployment_rolling_restart.py b/examples/dynamic-client/deployment_rolling_restart.py new file mode 100644 index 0000000000..8218e9e711 --- /dev/null +++ b/examples/dynamic-client/deployment_rolling_restart.py @@ -0,0 +1,120 @@ +# 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 deployment using dynamic-client + - Rolling restart of the deployment (demonstrate patch/update action) + - Listing & deletion of the deployment +""" + + +from kubernetes import config, dynamic +from kubernetes.client import api_client +import datetime +import pytz + +def main(): + # Creating a dynamic client + client = dynamic.DynamicClient( + api_client.ApiClient(configuration=config.load_kube_config()) + ) + + # fetching the deployment api + api = client.resources.get(api_version="apps/v1", kind="Deployment") + + name = "nginx-deployment" + + deployment_manifest = { + "apiVersion": "apps/v1", + "kind": "Deployment", + "metadata": {"labels": {"app": "nginx"}, "name": name}, + "spec": { + "replicas": 3, + "selector": {"matchLabels": {"app": "nginx"}}, + "template": { + "metadata": {"labels": {"app": "nginx"}}, + "spec": { + "containers": [ + { + "name": "nginx", + "image": "nginx:1.14.2", + "ports": [{"containerPort": 80}], + } + ] + }, + }, + }, + } + + # Creating deployment `nginx-deployment` in the `default` namespace + + deployment = api.create(body=deployment_manifest, namespace="default") + + print("\n[INFO] deployment `nginx-deployment` created\n") + + # Listing deployment `nginx-deployment` in the `default` namespace + + deployment_created = api.get(name=name, namespace="default") + + print("%s\t%s\t\t\t%s\t%s" % ("NAMESPACE", "NAME", "REVISION", "RESTARTED-AT")) + print( + "%s\t\t%s\t%s\t\t%s\n" + % ( + deployment_created.metadata.namespace, + deployment_created.metadata.name, + deployment_created.metadata.annotations, + deployment_created.spec.template.metadata.annotations, + ) + ) + + # Patching the `spec.template.metadata` section to add `kubectl.kubernetes.io/restartedAt` annotation + # In order to perform a rolling restart on the deployment `nginx-deployment` + + deployment_manifest["spec"]["template"]["metadata"] = { + "annotations": { + "kubectl.kubernetes.io/restartedAt": datetime.datetime.utcnow() + .replace(tzinfo=pytz.UTC) + .isoformat() + } + } + + deployment_patched = api.patch( + body=deployment_manifest, name=name, namespace="default" + ) + + print("\n[INFO] deployment `nginx-deployment` restarted\n") + print( + "%s\t%s\t\t\t%s\t\t\t\t\t\t%s" + % ("NAMESPACE", "NAME", "REVISION", "RESTARTED-AT") + ) + print( + "%s\t\t%s\t%s\t\t%s\n" + % ( + deployment_patched.metadata.namespace, + deployment_patched.metadata.name, + deployment_patched.metadata.annotations, + deployment_patched.spec.template.metadata.annotations, + ) + ) + + # Deleting deployment `nginx-deployment` from the `default` namespace + + deployment_deleted = api.delete(name=name, body={}, namespace="default") + + print("\n[INFO] deployment `nginx-deployment` deleted\n") + + +if __name__ == "__main__": + main() diff --git a/examples/dynamic-client/namespaced_custom_resource.py b/examples/dynamic-client/namespaced_custom_resource.py new file mode 100644 index 0000000000..c577672a37 --- /dev/null +++ b/examples/dynamic-client/namespaced_custom_resource.py @@ -0,0 +1,250 @@ +# 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 namespaced custom resources (CR) using the above CRD + - List, patch (update), delete the custom resources + - Delete the custom resource defintion (CRD) +""" + +from kubernetes import config, dynamic +from kubernetes import client as k8s_client +from kubernetes.dynamic.exceptions import ResourceNotFoundError +from kubernetes.client import api_client +import time + +def list_ingressroute_for_all_namespaces(group, version, plural): + custom_object_api = k8s_client.CustomObjectsApi() + + list_of_ingress_routes = custom_object_api.list_cluster_custom_object( + group, version, plural + ) + print( + "%s\t\t\t%s\t\t\t%s\t\t%s\t\t\t\t%s" + % ("NAME", "NAMESPACE", "FQDN", "TLS", "STRATEGY") + ) + for item in list_of_ingress_routes["items"]: + print( + "%s\t%s\t\t%s\t%s\t%s" + % ( + item["metadata"]["name"], + item["metadata"]["namespace"], + item["spec"]["virtualhost"]["fqdn"], + item["spec"]["virtualhost"]["tls"], + item["spec"]["strategy"] + ) + ) + +def create_namespace(namespace_api, name): + namespace_manifest = { + "apiVersion": "v1", + "kind": "Namespace", + "metadata": {"name": name, "resourceversion": "v1"}, + } + namespace_api.create(body=namespace_manifest) + + +def delete_namespace(namespace_api, name): + namespace_api.delete(name=name) + +def main(): + # Creating a dynamic client + client = dynamic.DynamicClient( + api_client.ApiClient(configuration=config.load_kube_config()) + ) + + # fetching the custom resource definition (CRD) api + crd_api = client.resources.get( + api_version="apiextensions.k8s.io/v1", kind="CustomResourceDefinition" + ) + + namespace_api = client.resources.get(api_version="v1", kind="Namespace") + + # 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, "namespace": "default"}, + "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": "Namespaced", + "names": { + "plural": "ingressroutes", + "listKind": "IngressRouteList", + "singular": "ingressroute", + "kind": "IngressRoute", + "shortNames": ["ir"], + }, + }, + } + + crd_creation_respone = crd_api.create(crd_manifest) + print( + "\n[INFO] custom resource definition `ingressroutes.apps.example.com` created\n" + ) + print("%s\t\t%s" % ("SCOPE", "NAME")) + print( + "%s\t%s\n" + % (crd_creation_respone.spec.scope, crd_creation_respone.metadata.name) + ) + + # Fetching the "ingressroutes" CRD api + + try: + ingressroute_api = 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 + time.sleep(2) + + ingressroute_api = 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` + + namespace_first = "test-namespace-first" + namespace_second = "test-namespace-second" + + create_namespace(namespace_api, namespace_first) + create_namespace(namespace_api, namespace_second) + + ingressroute_manifest_first = { + "apiVersion": "apps.example.com/v1", + "kind": "IngressRoute", + "metadata": { + "name": "ingress-route-first", + "namespace": namespace_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", + "namespace": namespace_second, + }, + "spec": { + "virtualhost": { + "fqdn": "www.yahoo.com", + "tls": {"secretName": "yahoo-tls"}, + }, + "strategy": "RoundRobin", + }, + } + + ingressroute_api.create(body=ingressroute_manifest_first, namespace=namespace_first) + ingressroute_api.create(body=ingressroute_manifest_second, namespace=namespace_second) + print("\n[INFO] custom resources `ingress-route-*` created\n") + + # Listing the `ingress-route-*` custom resources + + list_ingressroute_for_all_namespaces( + group="apps.example.com", version="v1", plural="ingressroutes" + ) + + # Patching the ingressroutes custom resources + + ingressroute_manifest_first["spec"]["strategy"] = "Random" + ingressroute_manifest_second["spec"]["strategy"] = "WeightedLeastRequest" + + patch_ingressroute_first = ingressroute_api.patch( + body=ingressroute_manifest_first, content_type="application/merge-patch+json" + ) + patch_ingressroute_second = 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" + ) + list_ingressroute_for_all_namespaces( + group="apps.example.com", version="v1", plural="ingressroutes" + ) + + # Deleting the ingressroutes custom resources + + delete_ingressroute_first = ingressroute_api.delete( + name="ingress-route-first", namespace=namespace_first + ) + delete_ingressroute_second = ingressroute_api.delete( + name="ingress-route-second", namespace=namespace_second + ) + + print("\n[INFO] custom resources `ingress-route-*` deleted") + + # Deleting the namespaces + + delete_namespace(namespace_api, namespace_first) + time.sleep(4) + delete_namespace(namespace_api, namespace_second) + time.sleep(4) + + print("\n[INFO] test namespaces deleted") + + # Deleting the ingressroutes.apps.example.com custom resource definition + + crd_api.delete(name=name) + print( + "\n[INFO] custom resource definition `ingressroutes.apps.example.com` deleted" + ) + + +if __name__ == "__main__": + main() diff --git a/examples/dynamic-client/node.py b/examples/dynamic-client/node.py new file mode 100644 index 0000000000..6dff9f5eac --- /dev/null +++ b/examples/dynamic-client/node.py @@ -0,0 +1,49 @@ +# 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 list cluster nodes using dynamic client. + +""" + +from kubernetes import config, dynamic +from kubernetes.client import api_client + + +def main(): + # Creating a dynamic client + client = dynamic.DynamicClient( + api_client.ApiClient(configuration=config.load_kube_config()) + ) + + # fetching the node api + api = client.resources.get(api_version="v1", kind="Node") + + # Listing cluster nodes + + print("%s\t\t%s\t\t%s" % ("NAME", "STATUS", "VERSION")) + for item in api.get().items: + node = api.get(name=item.metadata.name) + print( + "%s\t%s\t\t%s\n" + % ( + node.metadata.name, + node.status.conditions[3]["type"], + node.status.nodeInfo.kubeProxyVersion, + ) + ) + + +if __name__ == "__main__": + main() diff --git a/examples/dynamic-client/replication_controller.py b/examples/dynamic-client/replication_controller.py new file mode 100644 index 0000000000..8c4bd6c21c --- /dev/null +++ b/examples/dynamic-client/replication_controller.py @@ -0,0 +1,84 @@ +# 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 creation, listing & deletion of a namespaced replication controller using dynamic-client. +""" + +from kubernetes import config, dynamic +from kubernetes.client import api_client + + +def main(): + # Creating a dynamic client + client = dynamic.DynamicClient( + api_client.ApiClient(configuration=config.load_kube_config()) + ) + + # fetching the replication controller api + api = client.resources.get(api_version="v1", kind="ReplicationController") + + name = "frontend-replication-controller" + + replication_controller_manifest = { + "apiVersion": "v1", + "kind": "ReplicationController", + "metadata": {"labels": {"name": name}, "name": name}, + "spec": { + "replicas": 2, + "selector": {"name": name}, + "template": { + "metadata": {"labels": {"name": name}}, + "spec": { + "containers": [ + { + "image": "nginx", + "name": "nginx", + "ports": [{"containerPort": 80, "protocol": "TCP"}], + } + ] + }, + }, + }, + } + + # Creating replication-controller `frontend-replication-controller` in the `default` namespace + replication_controller = api.create( + body=replication_controller_manifest, namespace="default" + ) + + print("\n[INFO] replication-controller `frontend-replication-controller` created\n") + + # Listing replication-controllers in the `default` namespace + replication_controller_created = api.get(name=name, namespace="default") + + print("%s\t%s\t\t\t\t\t%s" % ("NAMESPACE", "NAME", "REPLICAS")) + print( + "%s\t\t%s\t\t%s\n" + % ( + replication_controller_created.metadata.namespace, + replication_controller_created.metadata.name, + replication_controller_created.spec.replicas, + ) + ) + + # Deleting replication-controller `frontend-service` from the `default` namespace + + replication_controller_deleted = api.delete(name=name, body={}, namespace="default") + + print("[INFO] replication-controller `frontend-replication-controller` deleted\n") + + +if __name__ == "__main__": + main() diff --git a/examples/dynamic-client/service.py b/examples/dynamic-client/service.py new file mode 100644 index 0000000000..63206fd001 --- /dev/null +++ b/examples/dynamic-client/service.py @@ -0,0 +1,89 @@ +# 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 service using dynamic-client + - List, patch(update), delete the service +""" + +from kubernetes import config, dynamic +from kubernetes.client import api_client + + +def main(): + # Creating a dynamic client + client = dynamic.DynamicClient( + api_client.ApiClient(configuration=config.load_kube_config()) + ) + + # fetching the service api + api = client.resources.get(api_version="v1", kind="Service") + + name = "frontend-service" + + service_manifest = { + "apiVersion": "v1", + "kind": "Service", + "metadata": {"labels": {"name": name}, "name": name, "resourceversion": "v1"}, + "spec": { + "ports": [ + {"name": "port", "port": 80, "protocol": "TCP", "targetPort": 80} + ], + "selector": {"name": name}, + }, + } + + # Creating service `frontend-service` in the `default` namespace + + service = api.create(body=service_manifest, namespace="default") + + print("\n[INFO] service `frontend-service` created\n") + + # Listing service `frontend-service` in the `default` namespace + service_created = api.get(name=name, namespace="default") + + print("%s\t%s" % ("NAMESPACE", "NAME")) + print( + "%s\t\t%s\n" + % (service_created.metadata.namespace, service_created.metadata.name) + ) + + # Patching the `spec` section of the `frontend-service` + + service_manifest["spec"]["ports"] = [ + {"name": "new", "port": 8080, "protocol": "TCP", "targetPort": 8080} + ] + + service_patched = api.patch(body=service_manifest, name=name, namespace="default") + + print("\n[INFO] service `frontend-service` patched\n") + print("%s\t%s\t\t\t%s" % ("NAMESPACE", "NAME", "PORTS")) + print( + "%s\t\t%s\t%s\n" + % ( + service_patched.metadata.namespace, + service_patched.metadata.name, + service_patched.spec.ports, + ) + ) + + # Deleting service `frontend-service` from the `default` namespace + service_deleted = api.delete(name=name, body={}, namespace="default") + + print("\n[INFO] service `frontend-service` deleted\n") + + +if __name__ == "__main__": + main() diff --git a/kubernetes/base b/kubernetes/base index 060cac10e5..b4d3aad42d 160000 --- a/kubernetes/base +++ b/kubernetes/base @@ -1 +1 @@ -Subproject commit 060cac10e53169c904e0d50b7448233829019e35 +Subproject commit b4d3aad42dc23e7a6c0e5c032691f8dc385a786c diff --git a/scripts/update-client.sh b/scripts/update-client.sh index f27ca60d4f..0145dd0298 100755 --- a/scripts/update-client.sh +++ b/scripts/update-client.sh @@ -24,22 +24,6 @@ set -o pipefail # The openapi-generator version used by this client export OPENAPI_GENERATOR_COMMIT="v4.3.0" -# OS X sed doesn't support "--version". This way we can tell if OS X sed is -# used. -if ! sed --version &>/dev/null; then - # OS X sed and GNU sed aren't compatible with backup flag "-i". Namely - # sed -i ... - does not work on OS X - # sed -i'' ... - does not work on certain OS X versions - # sed -i '' ... - does not work on GNU - echo ">>> OS X sed detected, which may be incompatible with this script. Please install and use GNU sed instead: - $ brew install gnu-sed - $ brew info gnu-sed - # Find the path to the installed gnu-sed and add it to your PATH. The default - # is: - # PATH=\"/Users/\$USER/homebrew/opt/gnu-sed/libexec/gnubin:\$PATH\"" - exit 1 -fi - SCRIPT_ROOT=$(dirname "${BASH_SOURCE}") CLIENT_ROOT="${SCRIPT_ROOT}/../kubernetes" CLIENT_VERSION=$(python "${SCRIPT_ROOT}/constants.py" CLIENT_VERSION) @@ -50,11 +34,14 @@ pushd "${SCRIPT_ROOT}" > /dev/null SCRIPT_ROOT=`pwd` popd > /dev/null +source ${SCRIPT_ROOT}/util/common.sh +util::common::check_sed + pushd "${CLIENT_ROOT}" > /dev/null CLIENT_ROOT=`pwd` popd > /dev/null -TEMP_FOLDER=$(mktemp -d) +TEMP_FOLDER=$(mktemp -d) trap "rm -rf ${TEMP_FOLDER}" EXIT SIGINT SETTING_FILE="${TEMP_FOLDER}/settings" diff --git a/scripts/update-submodule.sh b/scripts/update-submodule.sh index 86aa5a4b5a..2ef51eebbc 100755 --- a/scripts/update-submodule.sh +++ b/scripts/update-submodule.sh @@ -31,11 +31,15 @@ set -o errexit set -o nounset set -o pipefail + repo_root="$(git rev-parse --show-toplevel)" declare -r repo_root cd "${repo_root}" source scripts/util/changelog.sh +source scripts/util/common.sh + +util::common::check_sed go get k8s.io/release/cmd/release-notes TARGET_RELEASE=${TARGET_RELEASE:-"v$(grep "^CLIENT_VERSION = \"" scripts/constants.py | sed "s/CLIENT_VERSION = \"//g" | sed "s/\"//g")"} diff --git a/scripts/util/common.sh b/scripts/util/common.sh new file mode 100644 index 0000000000..0955d597b2 --- /dev/null +++ b/scripts/util/common.sh @@ -0,0 +1,35 @@ +#!/bin/bash + +# 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. + +# check_sed returns an error and suggests installing GNU sed, if OS X sed is +# detected. +function util::common::check_sed { + # OS X sed doesn't support "--version". This way we can tell if OS X sed is + # used. + if ! sed --version &>/dev/null; then + # OS X sed and GNU sed aren't compatible with backup flag "-i". Namely + # sed -i ... - does not work on OS X + # sed -i'' ... - does not work on certain OS X versions + # sed -i '' ... - does not work on GNU + echo ">>> OS X sed detected, which may be incompatible with this script. Please install and use GNU sed instead: + $ brew install gnu-sed + $ brew info gnu-sed + # Find the path to the installed gnu-sed and add it to your PATH. The default + # is: + # PATH=\"/Users/\$USER/homebrew/opt/gnu-sed/libexec/gnubin:\$PATH\"" + exit 1 + fi +}