Merge branch 'master' of github.com:kubernetes-client/python into pull-master-into-release-17.0
This commit is contained in:
commit
b181532fe3
72
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
72
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
@ -0,0 +1,72 @@
|
||||
<!-- Thanks for sending a pull request! Here are some tips for you:
|
||||
|
||||
1. If this is your first time, please read our contributor guidelines: https://git.k8s.io/community/contributors/guide/first-contribution.md#your-first-contribution and developer guide https://git.k8s.io/community/contributors/devel/development.md#development-guide
|
||||
2. Please label this pull request according to what type of issue you are addressing, especially if this is a release targeted pull request. For reference on required PR/issue labels, read here:
|
||||
https://git.k8s.io/community/contributors/devel/sig-release/release.md#issuepr-kind-label
|
||||
3. Ensure you have added or ran the appropriate tests for your PR: https://git.k8s.io/community/contributors/devel/sig-testing/testing.md
|
||||
4. If you want *faster* PR reviews, read how: https://git.k8s.io/community/contributors/guide/pull-requests.md#best-practices-for-faster-reviews
|
||||
5. If the PR is unfinished, see how to mark it: https://git.k8s.io/community/contributors/guide/pull-requests.md#marking-unfinished-pull-requests
|
||||
-->
|
||||
|
||||
#### What type of PR is this?
|
||||
|
||||
<!--
|
||||
Add one of the following kinds:
|
||||
/kind bug
|
||||
/kind cleanup
|
||||
/kind documentation
|
||||
/kind feature
|
||||
/kind design
|
||||
|
||||
Optionally add one or more of the following kinds if applicable:
|
||||
/kind api-change
|
||||
/kind deprecation
|
||||
/kind failing-test
|
||||
/kind flake
|
||||
/kind regression
|
||||
-->
|
||||
|
||||
#### What this PR does / why we need it:
|
||||
|
||||
#### Which issue(s) this PR fixes:
|
||||
<!--
|
||||
*Automatically closes linked issue when PR is merged.
|
||||
Usage: `Fixes #<issue number>`, or `Fixes (paste link of issue)`.
|
||||
_If PR is about `failing-tests or flakes`, please post the related issues/tests in a comment and do not use `Fixes`_*
|
||||
-->
|
||||
Fixes #
|
||||
|
||||
#### Special notes for your reviewer:
|
||||
|
||||
#### Does this PR introduce a user-facing change?
|
||||
<!--
|
||||
If no, just write "NONE" in the release-note block below.
|
||||
If yes, a release note is required:
|
||||
Enter your extended release note in the block below. If the PR requires additional action from users switching to the new release, include the string "action required".
|
||||
|
||||
For more information on release notes see: https://git.k8s.io/community/contributors/guide/release-notes.md
|
||||
-->
|
||||
```release-note
|
||||
|
||||
```
|
||||
|
||||
#### Additional documentation e.g., KEPs (Kubernetes Enhancement Proposals), usage docs, etc.:
|
||||
|
||||
<!--
|
||||
This section can be blank if this pull request does not require a release note.
|
||||
|
||||
When adding links which point to resources within git repositories, like
|
||||
KEPs or supporting documentation, please reference a specific commit and avoid
|
||||
linking directly to the master branch. This ensures that links reference a
|
||||
specific point in time, rather than a document that may change over time.
|
||||
|
||||
See here for guidance on getting permanent links to files: https://help.github.com/en/articles/getting-permanent-links-to-files
|
||||
|
||||
Please use the following format for linking documentation:
|
||||
- [KEP]: <link>
|
||||
- [Usage]: <link>
|
||||
- [Other doc]: <link>
|
||||
-->
|
||||
```docs
|
||||
|
||||
```
|
||||
4
.github/workflows/test.yaml
vendored
4
.github/workflows/test.yaml
vendored
@ -8,14 +8,14 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: [2.7, 3.5, 3.6, 3.7, 3.8]
|
||||
python-version: [2.7, 3.5, 3.6, 3.7, 3.8, 3.9]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
submodules: true
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v2.1.4
|
||||
uses: actions/setup-python@v2.2.2
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
- name: Install dependencies
|
||||
|
||||
54
.travis.yml
54
.travis.yml
@ -1,5 +1,5 @@
|
||||
language: python
|
||||
dist: xenial
|
||||
dist: bionic
|
||||
services:
|
||||
- docker
|
||||
|
||||
@ -13,9 +13,16 @@ jobs:
|
||||
include:
|
||||
- stage: verify-tag
|
||||
python: 3.7
|
||||
arch: ppc64le
|
||||
script: >
|
||||
[ "v$(python -c 'from scripts.constants import CLIENT_VERSION; print(CLIENT_VERSION)')" == "${TRAVIS_TAG}" ] &&
|
||||
[[ "${TRAVIS_TAG}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+(([ab]|dev|rc)[0-9]+)?$ ]]
|
||||
- stage: verify-tag
|
||||
python: 3.7
|
||||
script: >
|
||||
[ "v$(python -c 'from scripts.constants import CLIENT_VERSION; print(CLIENT_VERSION)')" == "${TRAVIS_TAG}" ] &&
|
||||
[[ "${TRAVIS_TAG}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+(([ab]|dev|rc)[0-9]+)?$ ]]
|
||||
|
||||
- stage: test
|
||||
python: 2.7
|
||||
env: TOXENV=update-pycodestyle
|
||||
@ -43,6 +50,10 @@ jobs:
|
||||
env: TOXENV=py38
|
||||
- python: 3.8
|
||||
env: TOXENV=py38-functional
|
||||
- python: 3.9
|
||||
env: TOXENV=py39
|
||||
- python: 3.9
|
||||
env: TOXENV=py39-functional
|
||||
- stage: deploy
|
||||
script: skip
|
||||
deploy:
|
||||
@ -56,6 +67,47 @@ jobs:
|
||||
repo: kubernetes-client/python
|
||||
distributions: sdist bdist_wheel
|
||||
|
||||
- stage: test
|
||||
python: 2.7
|
||||
env: TOXENV=update-pycodestyle
|
||||
arch: ppc64le
|
||||
- python: 3.7
|
||||
env: TOXENV=docs
|
||||
arch: ppc64le
|
||||
- python: 2.7
|
||||
env: TOXENV=coverage,codecov
|
||||
arch: ppc64le
|
||||
- python: 2.7
|
||||
env: TOXENV=py27
|
||||
arch: ppc64le
|
||||
- python: 3.5
|
||||
env: TOXENV=py35
|
||||
arch: ppc64le
|
||||
- python: 3.6
|
||||
env: TOXENV=py36
|
||||
arch: ppc64le
|
||||
- python: 3.7
|
||||
env: TOXENV=py37
|
||||
arch: ppc64le
|
||||
- python: 3.8
|
||||
env: TOXENV=py38
|
||||
- python: 3.9
|
||||
env: TOXENV=py39
|
||||
arch: ppc64le
|
||||
- stage: deploy
|
||||
script: skip
|
||||
arch: ppc64le
|
||||
deploy:
|
||||
provider: pypi
|
||||
user: __token__
|
||||
password:
|
||||
secure: gY5Rixj7mWHC9XP5qV5DfWGdX4ZVwCEUElnQA2OeIg235I3eMBqRFM4Q/SKwAG2DzgIWNKsXXVQsZHp7BAjWFMFVQloiU7zohuBRToJUim9U1RaqAjUIr4OU7JPtXenAl5zyyBdywvJiG8UZ4wmt1DBYtdpozQvOwDXvOxNTmElKh5mfDhiSsipmFr2198NtIhiRVC+CZliZsi6osUkt+G6yl9CW+SJU3otgzdaS+VBP26HO0kWHMJiDKvQoIl/Q50IqJUWieFhCLh7lSV71VNVEmM4bMcYK8cAv3zMZHo6REKHF7xrF5tzYMXqpmEGt6L798d2H4BISr6BIlYgiYCatjyE9hxih9iBzGs0XaGUUFD8u1iuzOQI76a5dapG/DixQrGD2o9Gn/Qw6Zp9USIuKZSWUn5hSobwxJUKVNy+afpaJNQUb2W9Hj+jMXAnBDodCzo3nu+QF8GN72cmk3uqVyKUVABtI4kNe3qcEx3DyKfoh7aqJrgydeaRwESKuZ41l5CA+vqXSbbNW8z1MYDYgVdwEyRFsLg6aQk5pPsxuiILaaGy13TUndhuC+GuKcW6wCDf6WpUAwwGAF8+sz4hZ1pfSUdE3F8nfDBW3Bv+G9cB/cKkWJ2vOd9httRrvir8qUc/xPP5aW4pacnfNCQ04Iep/k4PCAdYJDtVGhCY=
|
||||
skip_existing: true
|
||||
on:
|
||||
tags: true
|
||||
repo: kubernetes-client/python
|
||||
distributions: sdist bdist_wheel
|
||||
|
||||
stages:
|
||||
- name: verify-tag
|
||||
if: (tag is present) and (type = push)
|
||||
|
||||
@ -23,7 +23,7 @@ git submodule update --init
|
||||
If you changed [kubernetes-client/python-base](https://github.com/kubernetes-client/python-base) and want to pull your changes into this repo run this command:
|
||||
|
||||
```bash
|
||||
git submodule update --remote
|
||||
scripts/update-submodule.sh
|
||||
```
|
||||
|
||||
Once updated, you should create a new PR to commit changes to the repository.
|
||||
After the script finishes, please create a commit "generated python-base update" and send a PR to this repository.
|
||||
|
||||
150
examples/cluster_scoped_custom_object.py
Normal file
150
examples/cluster_scoped_custom_object.py
Normal file
@ -0,0 +1,150 @@
|
||||
# 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.
|
||||
|
||||
"""
|
||||
Uses a Custom Resource Definition (CRD) to create a Custom Resource (CR), in this case
|
||||
a CronTab. This example use an example CRD from this tutorial:
|
||||
https://kubernetes.io/docs/tasks/access-kubernetes-api/custom-resources/custom-resource-definitions/
|
||||
|
||||
Apply the following yaml manifest to create a cluster-scoped CustomResourceDefinition (CRD)
|
||||
|
||||
apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
name: crontabs.stable.example.com
|
||||
spec:
|
||||
group: stable.example.com
|
||||
versions:
|
||||
- name: v1
|
||||
served: true
|
||||
storage: true
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
type: object
|
||||
properties:
|
||||
spec:
|
||||
type: object
|
||||
properties:
|
||||
cronSpec:
|
||||
type: string
|
||||
image:
|
||||
type: string
|
||||
replicas:
|
||||
type: integer
|
||||
scope: Cluster
|
||||
names:
|
||||
plural: crontabs
|
||||
singular: crontab
|
||||
kind: CronTab
|
||||
shortNames:
|
||||
- ct
|
||||
"""
|
||||
|
||||
from pprint import pprint
|
||||
|
||||
from kubernetes import client, config
|
||||
|
||||
|
||||
def main():
|
||||
config.load_kube_config()
|
||||
|
||||
api = client.CustomObjectsApi()
|
||||
|
||||
# definition of custom resource
|
||||
test_resource = {
|
||||
"apiVersion": "stable.example.com/v1",
|
||||
"kind": "CronTab",
|
||||
"metadata": {"name": "test-crontab"},
|
||||
"spec": {"cronSpec": "* * * * */5", "image": "my-awesome-cron-image"},
|
||||
}
|
||||
|
||||
# patch to update the `spec.cronSpec` field
|
||||
cronspec_patch = {
|
||||
"spec": {"cronSpec": "* * * * */15", "image": "my-awesome-cron-image"}
|
||||
}
|
||||
|
||||
# patch to add the `metadata.labels` field
|
||||
metadata_label_patch = {
|
||||
"metadata": {
|
||||
"labels": {
|
||||
"foo": "bar",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# create a cluster scoped resource
|
||||
created_resource = api.create_cluster_custom_object(
|
||||
group="stable.example.com",
|
||||
version="v1",
|
||||
plural="crontabs",
|
||||
body=test_resource,
|
||||
)
|
||||
print("[INFO] Custom resource `test-crontab` created!\n")
|
||||
|
||||
# get the cluster scoped resource
|
||||
resource = api.get_cluster_custom_object(
|
||||
group="stable.example.com",
|
||||
version="v1",
|
||||
name="test-crontab",
|
||||
plural="crontabs",
|
||||
)
|
||||
print("%s\t\t%s" % ("NAME", "CRON-SPEC"))
|
||||
print(
|
||||
"%s\t%s\n" %
|
||||
(resource["metadata"]["name"],
|
||||
resource["spec"]["cronSpec"]))
|
||||
|
||||
# patch the `spec.cronSpec` field of the custom resource
|
||||
patched_resource = api.patch_cluster_custom_object(
|
||||
group="stable.example.com",
|
||||
version="v1",
|
||||
plural="crontabs",
|
||||
name="test-crontab",
|
||||
body=cronspec_patch,
|
||||
)
|
||||
print("[INFO] Custom resource `test-crontab` patched to update the cronSpec schedule!\n")
|
||||
print("%s\t\t%s" % ("NAME", "PATCHED-CRON-SPEC"))
|
||||
print(
|
||||
"%s\t%s\n" %
|
||||
(patched_resource["metadata"]["name"],
|
||||
patched_resource["spec"]["cronSpec"]))
|
||||
|
||||
# patch the `metadata.labels` field of the custom resource
|
||||
patched_resource = api.patch_cluster_custom_object(
|
||||
group="stable.example.com",
|
||||
version="v1",
|
||||
plural="crontabs",
|
||||
name="test-crontab",
|
||||
body=metadata_label_patch,
|
||||
)
|
||||
print("[INFO] Custom resource `test-crontab` patched to apply new metadata labels!\n")
|
||||
print("%s\t\t%s" % ("NAME", "PATCHED_LABELS"))
|
||||
print(
|
||||
"%s\t%s\n" %
|
||||
(patched_resource["metadata"]["name"],
|
||||
patched_resource["metadata"]["labels"]))
|
||||
|
||||
# delete the custom resource "test-crontab"
|
||||
api.delete_cluster_custom_object(
|
||||
group="stable.example.com",
|
||||
version="v1",
|
||||
name="test-crontab",
|
||||
plural="crontabs",
|
||||
body=client.V1DeleteOptions(),
|
||||
)
|
||||
print("[INFO] Custom resource `test-crontab` deleted!")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@ -12,6 +12,10 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
"""
|
||||
Creates a deployment using AppsV1Api from file nginx-deployment.yaml.
|
||||
"""
|
||||
|
||||
from os import path
|
||||
|
||||
import yaml
|
||||
|
||||
@ -17,7 +17,7 @@ Uses a Custom Resource Definition (CRD) to create a custom object, in this case
|
||||
a CronTab. This example use an example CRD from this tutorial:
|
||||
https://kubernetes.io/docs/tasks/access-kubernetes-api/custom-resources/custom-resource-definitions/
|
||||
|
||||
The following yaml manifest has to be applied first:
|
||||
The following yaml manifest has to be applied first for namespaced scoped CRD:
|
||||
|
||||
apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
@ -29,6 +29,19 @@ spec:
|
||||
- name: v1
|
||||
served: true
|
||||
storage: true
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
type: object
|
||||
properties:
|
||||
spec:
|
||||
type: object
|
||||
properties:
|
||||
cronSpec:
|
||||
type: string
|
||||
image:
|
||||
type: string
|
||||
replicas:
|
||||
type: integer
|
||||
scope: Namespaced
|
||||
names:
|
||||
plural: crontabs
|
||||
@ -59,6 +72,11 @@ def main():
|
||||
}
|
||||
}
|
||||
|
||||
# patch to update the `spec.cronSpec` field
|
||||
patch_body = {
|
||||
"spec": {"cronSpec": "* * * * */10", "image": "my-awesome-cron-image"}
|
||||
}
|
||||
|
||||
# create the resource
|
||||
api.create_namespaced_custom_object(
|
||||
group="stable.example.com",
|
||||
@ -80,6 +98,18 @@ def main():
|
||||
print("Resource details:")
|
||||
pprint(resource)
|
||||
|
||||
# patch the namespaced custom object to update the `spec.cronSpec` field
|
||||
patch_resource = api.patch_namespaced_custom_object(
|
||||
group="stable.example.com",
|
||||
version="v1",
|
||||
name="my-new-cron-object",
|
||||
namespace="default",
|
||||
plural="crontabs",
|
||||
body=patch_body,
|
||||
)
|
||||
print("Resource details:")
|
||||
pprint(patch_resource)
|
||||
|
||||
# delete it
|
||||
api.delete_namespaced_custom_object(
|
||||
group="stable.example.com",
|
||||
@ -13,13 +13,14 @@
|
||||
# limitations under the License.
|
||||
|
||||
"""
|
||||
Changes the labels of the "minikube" node. Adds the label "foo" with value
|
||||
"bar" and will overwrite the "foo" label if it already exists. Removes the
|
||||
label "baz".
|
||||
This example demonstrates the following:
|
||||
- Get a list of all the cluster nodes
|
||||
- Iterate through each node list item
|
||||
- Add or overwirite label "foo" with the value "bar"
|
||||
- Remove the label "baz"
|
||||
- Return the list of node with updated labels
|
||||
"""
|
||||
|
||||
from pprint import pprint
|
||||
|
||||
from kubernetes import client, config
|
||||
|
||||
|
||||
@ -36,9 +37,14 @@ def main():
|
||||
}
|
||||
}
|
||||
|
||||
api_response = api_instance.patch_node("minikube", body)
|
||||
# Listing the cluster nodes
|
||||
node_list = api_instance.list_node()
|
||||
|
||||
pprint(api_response)
|
||||
print("%s\t\t%s" % ("NAME", "LABELS"))
|
||||
# Patching the node labels
|
||||
for node in node_list.items:
|
||||
api_response = api_instance.patch_node(node.metadata.name, body)
|
||||
print("%s\t%s" % (node.metadata.name, node.metadata.labels))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
@ -117,8 +117,11 @@ def exec_commands(api_instance):
|
||||
|
||||
def main():
|
||||
config.load_kube_config()
|
||||
c = Configuration()
|
||||
c.assert_hostname = False
|
||||
try:
|
||||
c = Configuration().get_default_copy()
|
||||
except AttributeError:
|
||||
c = Configuration()
|
||||
c.assert_hostname = False
|
||||
Configuration.set_default(c)
|
||||
core_v1 = core_v1_api.CoreV1Api()
|
||||
|
||||
|
||||
@ -12,15 +12,17 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
# This example demonstrate communication with a remote Kube cluster from a
|
||||
# server outside of the cluster without kube client installed on it.
|
||||
# The communication is secured with the use of Bearer token.
|
||||
"""
|
||||
This example demonstrates the communication between a remote cluster and a
|
||||
server outside the cluster without kube client installed on it.
|
||||
The communication is secured with the use of Bearer token.
|
||||
"""
|
||||
|
||||
from kubernetes import client, config
|
||||
|
||||
|
||||
def main():
|
||||
# Define the barer token we are going to use to authenticate.
|
||||
# Define the bearer token we are going to use to authenticate.
|
||||
# See here to create the token:
|
||||
# https://kubernetes.io/docs/tasks/access-application-cluster/access-cluster/
|
||||
aToken = "<token>"
|
||||
|
||||
@ -22,3 +22,4 @@ import kubernetes.dynamic
|
||||
import kubernetes.watch
|
||||
import kubernetes.stream
|
||||
import kubernetes.utils
|
||||
import kubernetes.leaderelection
|
||||
|
||||
@ -1 +1 @@
|
||||
Subproject commit 2da2b981ca806b25487ad92d01a2164815c18517
|
||||
Subproject commit 060cac10e53169c904e0d50b7448233829019e35
|
||||
41
kubernetes/e2e_test/port_server.py
Normal file
41
kubernetes/e2e_test/port_server.py
Normal file
@ -0,0 +1,41 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
import select
|
||||
import socketserver
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
|
||||
|
||||
class PortServer:
|
||||
def __init__(self, port):
|
||||
self.port = port
|
||||
self.server = socketserver.ThreadingTCPServer(('0.0.0.0', port), self.handler)
|
||||
self.server.daemon_threads = True
|
||||
self.thread = threading.Thread(target=self.server.serve_forever,
|
||||
name='Port %s Server' % port)
|
||||
self.thread.daemon = True
|
||||
self.thread.start()
|
||||
|
||||
|
||||
def handler(self, request, address, server):
|
||||
threading.current_thread().name = 'Port %s Handler' % self.port
|
||||
rlist = [request]
|
||||
echo = b''
|
||||
while True:
|
||||
r, w, _x = select.select(rlist, [request] if echo else [], [])
|
||||
if r:
|
||||
data = request.recv(1024)
|
||||
if not data:
|
||||
break
|
||||
echo += data
|
||||
if w:
|
||||
echo = echo[request.send(echo):]
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
ports = []
|
||||
for port in sys.argv[1:]:
|
||||
ports.append(PortServer(int(port)))
|
||||
time.sleep(10 * 60)
|
||||
|
||||
@ -13,20 +13,28 @@
|
||||
# under the License.
|
||||
|
||||
import json
|
||||
import os
|
||||
import select
|
||||
import socket
|
||||
import time
|
||||
import unittest
|
||||
import uuid
|
||||
import six
|
||||
|
||||
from kubernetes.client import api_client
|
||||
from kubernetes.client.api import core_v1_api
|
||||
from kubernetes.e2e_test import base
|
||||
from kubernetes.stream import stream, portforward
|
||||
from kubernetes.stream.ws_client import ERROR_CHANNEL
|
||||
from kubernetes.client.rest import ApiException
|
||||
|
||||
import six.moves.urllib.request as urllib_request
|
||||
|
||||
if six.PY3:
|
||||
from http import HTTPStatus
|
||||
else:
|
||||
import httplib
|
||||
|
||||
def short_uuid():
|
||||
id = str(uuid.uuid4())
|
||||
return id[-12:]
|
||||
@ -64,6 +72,27 @@ class TestClient(unittest.TestCase):
|
||||
|
||||
name = 'busybox-test-' + short_uuid()
|
||||
pod_manifest = manifest_with_command(name, "while true;do date;sleep 5; done")
|
||||
|
||||
# wait for the default service account to be created
|
||||
timeout = time.time() + 30
|
||||
while True:
|
||||
if time.time() > timeout:
|
||||
print('timeout waiting for default service account creation')
|
||||
break
|
||||
try:
|
||||
resp = api.read_namespaced_service_account(name='default',
|
||||
namespace='default')
|
||||
except ApiException as e:
|
||||
if (six.PY3 and e.status != HTTPStatus.NOT_FOUND) or (
|
||||
six.PY3 is False and e.status != httplib.NOT_FOUND):
|
||||
print('error: %s' % e)
|
||||
self.fail(msg="unexpected error getting default service account")
|
||||
print('default service not found yet: %s' % e)
|
||||
time.sleep(1)
|
||||
continue
|
||||
self.assertEqual('default', resp.metadata.name)
|
||||
break
|
||||
|
||||
resp = api.create_namespaced_pod(body=pod_manifest,
|
||||
namespace='default')
|
||||
self.assertEqual(name, resp.metadata.name)
|
||||
@ -129,6 +158,28 @@ class TestClient(unittest.TestCase):
|
||||
|
||||
name = 'busybox-test-' + short_uuid()
|
||||
pod_manifest = manifest_with_command(name, "while true;do date;sleep 5; done")
|
||||
|
||||
# wait for the default service account to be created
|
||||
timeout = time.time() + 30
|
||||
while True:
|
||||
if time.time() > timeout:
|
||||
print('timeout waiting for default service account creation')
|
||||
break
|
||||
|
||||
try:
|
||||
resp = api.read_namespaced_service_account(name='default',
|
||||
namespace='default')
|
||||
except ApiException as e:
|
||||
if (six.PY3 and e.status != HTTPStatus.NOT_FOUND) or (
|
||||
six.PY3 is False and e.status != httplib.NOT_FOUND):
|
||||
print('error: %s' % e)
|
||||
self.fail(msg="unexpected error getting default service account")
|
||||
print('default service not found yet: %s' % e)
|
||||
time.sleep(1)
|
||||
continue
|
||||
self.assertEqual('default', resp.metadata.name)
|
||||
break
|
||||
|
||||
resp = api.create_namespaced_pod(body=pod_manifest,
|
||||
namespace='default')
|
||||
self.assertEqual(name, resp.metadata.name)
|
||||
@ -167,17 +218,64 @@ class TestClient(unittest.TestCase):
|
||||
client = api_client.ApiClient(configuration=self.config)
|
||||
api = core_v1_api.CoreV1Api(client)
|
||||
|
||||
with open(os.path.join(os.path.dirname(__file__), 'port_server.py')) as fh:
|
||||
port_server_py = fh.read()
|
||||
name = 'portforward-raw-' + short_uuid()
|
||||
pod_manifest = manifest_with_command(
|
||||
name,
|
||||
' '.join((
|
||||
'((while true;do nc -l -p 1234 -e /bin/cat; done)&);',
|
||||
'((while true;do nc -l -p 1235 -e /bin/cat; done)&);',
|
||||
'sleep 60',
|
||||
))
|
||||
resp = api.create_namespaced_config_map(
|
||||
body={
|
||||
'apiVersion': 'v1',
|
||||
'kind': 'ConfigMap',
|
||||
'metadata': {
|
||||
'name': name,
|
||||
},
|
||||
'data': {
|
||||
'port-server.py': port_server_py,
|
||||
}
|
||||
},
|
||||
namespace='default',
|
||||
)
|
||||
resp = api.create_namespaced_pod(
|
||||
body={
|
||||
'apiVersion': 'v1',
|
||||
'kind': 'Pod',
|
||||
'metadata': {
|
||||
'name': name
|
||||
},
|
||||
'spec': {
|
||||
'containers': [
|
||||
{
|
||||
'name': 'port-server',
|
||||
'image': 'python',
|
||||
'command': [
|
||||
'/opt/port-server.py', '1234', '1235',
|
||||
],
|
||||
'volumeMounts': [
|
||||
{
|
||||
'name': 'port-server',
|
||||
'mountPath': '/opt',
|
||||
'readOnly': True,
|
||||
},
|
||||
],
|
||||
'startupProbe': {
|
||||
'tcpSocket': {
|
||||
'port': 1234,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
'volumes': [
|
||||
{
|
||||
'name': 'port-server',
|
||||
'configMap': {
|
||||
'name': name,
|
||||
'defaultMode': 0o777,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
namespace='default',
|
||||
)
|
||||
resp = api.create_namespaced_pod(body=pod_manifest,
|
||||
namespace='default')
|
||||
self.assertEqual(name, resp.metadata.name)
|
||||
self.assertTrue(resp.status.phase)
|
||||
|
||||
@ -189,6 +287,7 @@ class TestClient(unittest.TestCase):
|
||||
if resp.status.phase != 'Pending':
|
||||
break
|
||||
time.sleep(1)
|
||||
self.assertEqual(resp.status.phase, 'Running')
|
||||
|
||||
pf = portforward(api.connect_get_namespaced_pod_portforward,
|
||||
name, 'default',
|
||||
@ -251,8 +350,8 @@ class TestClient(unittest.TestCase):
|
||||
self.assertIsNone(pf.error(1234))
|
||||
self.assertIsNone(pf.error(1235))
|
||||
|
||||
resp = api.delete_namespaced_pod(name=name, body={},
|
||||
namespace='default')
|
||||
resp = api.delete_namespaced_pod(name=name, namespace='default')
|
||||
resp = api.delete_namespaced_config_map(name=name, namespace='default')
|
||||
|
||||
def test_portforward_http(self):
|
||||
client = api_client.ApiClient(configuration=self.config)
|
||||
@ -394,6 +493,7 @@ class TestClient(unittest.TestCase):
|
||||
"apiVersion": "v1",
|
||||
"metadata": {
|
||||
"name": name,
|
||||
"labels": {"e2e-tests": "true"},
|
||||
},
|
||||
"data": {
|
||||
"config.json": "{\"command\":\"/usr/bin/mysqld_safe\"}",
|
||||
@ -417,7 +517,7 @@ class TestClient(unittest.TestCase):
|
||||
resp = api.delete_namespaced_config_map(
|
||||
name=name, body={}, namespace='default')
|
||||
|
||||
resp = api.list_namespaced_config_map('default', pretty=True)
|
||||
resp = api.list_namespaced_config_map('default', pretty=True, label_selector="e2e-tests=true")
|
||||
self.assertEqual([], resp.items)
|
||||
|
||||
def test_node_apis(self):
|
||||
|
||||
@ -32,6 +32,7 @@ def config_map_with_value(name, value):
|
||||
"kind": "ConfigMap",
|
||||
"metadata": {
|
||||
"name": name,
|
||||
"labels": {"e2e-tests": "true"},
|
||||
},
|
||||
"data": {
|
||||
"key": value,
|
||||
@ -57,7 +58,7 @@ class TestClient(unittest.TestCase):
|
||||
body=configmap_a, namespace='default')
|
||||
|
||||
# list all configmaps and extract the resource version
|
||||
resp = api.list_namespaced_config_map('default')
|
||||
resp = api.list_namespaced_config_map('default', label_selector="e2e-tests=true")
|
||||
rv = resp.metadata.resource_version
|
||||
|
||||
# create another configmap
|
||||
@ -73,7 +74,7 @@ class TestClient(unittest.TestCase):
|
||||
|
||||
# delete all configmaps
|
||||
api.delete_collection_namespaced_config_map(
|
||||
namespace='default')
|
||||
namespace='default', label_selector="e2e-tests=true")
|
||||
|
||||
w = watch.Watch()
|
||||
# expect to observe all events happened after the initial LIST
|
||||
@ -83,7 +84,8 @@ class TestClient(unittest.TestCase):
|
||||
for event in w.stream(api.list_namespaced_config_map,
|
||||
namespace='default',
|
||||
resource_version=rv,
|
||||
timeout_seconds=5):
|
||||
timeout_seconds=5,
|
||||
label_selector="e2e-tests=true"):
|
||||
self.assertEqual(event['type'], expect[i])
|
||||
# Kubernetes doesn't guarantee the order of the two objects
|
||||
# being deleted
|
||||
|
||||
1
kubernetes/leaderelection
Symbolic link
1
kubernetes/leaderelection
Symbolic link
@ -0,0 +1 @@
|
||||
base/leaderelection
|
||||
@ -20,6 +20,9 @@ import yaml
|
||||
|
||||
from kubernetes import client
|
||||
|
||||
UPPER_FOLLOWED_BY_LOWER_RE = re.compile('(.)([A-Z][a-z]+)')
|
||||
LOWER_OR_NUM_FOLLOWED_BY_UPPER_RE = re.compile('([a-z0-9])([A-Z])')
|
||||
|
||||
|
||||
def create_from_yaml(
|
||||
k8s_client,
|
||||
@ -155,8 +158,8 @@ def create_from_yaml_single_item(
|
||||
k8s_api = getattr(client, fcn_to_call)(k8s_client)
|
||||
# Replace CamelCased action_type into snake_case
|
||||
kind = yml_object["kind"]
|
||||
kind = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', kind)
|
||||
kind = re.sub('([a-z0-9])([A-Z])', r'\1_\2', kind).lower()
|
||||
kind = UPPER_FOLLOWED_BY_LOWER_RE.sub(r'\1_\2', kind)
|
||||
kind = LOWER_OR_NUM_FOLLOWED_BY_UPPER_RE.sub(r'\1_\2', kind).lower()
|
||||
# Expect the user to create namespaced objects more often
|
||||
if hasattr(k8s_api, "create_namespaced_{0}".format(kind)):
|
||||
# Decide which namespace we are going to put the object in,
|
||||
|
||||
@ -38,7 +38,7 @@ fi
|
||||
# UPDATE: The commit being cherry-picked is updated since the the client generated in 1adaaecd0879d7315f48259ad8d6cbd66b835385
|
||||
# differs from the initial hotfix
|
||||
# Ref: https://github.com/kubernetes-client/python/pull/995/commits/9959273625b999ae9a8f0679c4def2ee7d699ede
|
||||
git cherry-pick -n a138dcbb7a9da972402a847ce982b027e0224e60
|
||||
git cherry-pick -n 9959273625b999ae9a8f0679c4def2ee7d699ede
|
||||
if [ $? -eq 0 ]
|
||||
then
|
||||
echo Succesfully patched changes for custom client behavior
|
||||
@ -51,7 +51,7 @@ fi
|
||||
# Patching commits for enabling from kubernetes import apis
|
||||
# UPDATE: The commit being cherry-picked is updated to include both the commits as one
|
||||
# Ref: https://github.com/kubernetes-client/python/blob/0976d59d6ff206f2f428cabc7a6b7b1144843b2a/kubernetes/client/apis/__init__.py
|
||||
git cherry-pick -n 228a29a982aee922831c3af4fef66a7846ce4bb8
|
||||
git cherry-pick -n 56ab983036bcb5c78eee91483c1e610da69216d1
|
||||
if [ $? -eq 0 ]
|
||||
then
|
||||
echo Succesfully patched changes for enabling from kubernetes import apis
|
||||
|
||||
@ -21,6 +21,25 @@ set -o errexit
|
||||
set -o nounset
|
||||
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)
|
||||
|
||||
65
scripts/update-submodule.sh
Executable file
65
scripts/update-submodule.sh
Executable file
@ -0,0 +1,65 @@
|
||||
#!/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.
|
||||
|
||||
|
||||
# Update python-base submodule and collect release notes.
|
||||
# Usage:
|
||||
#
|
||||
# $ scripts/update-submodule.sh
|
||||
#
|
||||
# # To update the release notes for a specific release (e.g. v18.17.0a1):
|
||||
# $ TARGET_RELEASE="v18.17.0a1" scripts/update-submodule.sh
|
||||
#
|
||||
# After the script finishes, please create a commit "generated python-base update"
|
||||
# and send a PR to this repository.
|
||||
# TODO(roycaihw): make the script send a PR
|
||||
|
||||
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
|
||||
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")"}
|
||||
|
||||
# update submodule
|
||||
git submodule update --remote
|
||||
|
||||
# download release notes
|
||||
start_sha=$(git diff | grep "^-Subproject commit " | sed 's/-Subproject commit //g')
|
||||
end_sha=$(git diff | grep "^+Subproject commit " | sed 's/+Subproject commit //g')
|
||||
output="/tmp/python-base-relnote.md"
|
||||
release-notes --dependencies=false --org kubernetes-client --repo python-base --start-sha $start_sha --end-sha $end_sha --output $output
|
||||
sed -i 's/(\[\#/(\[kubernetes-client\/python-base\#/g' $output
|
||||
|
||||
# update changelog
|
||||
IFS_backup=$IFS
|
||||
IFS=$'\n'
|
||||
sections=($(grep "^### " $output))
|
||||
IFS=$IFS_backup
|
||||
for section in "${sections[@]}"; do
|
||||
# ignore section titles and empty lines; replace newline with liternal "\n"
|
||||
release_notes=$(sed -n "/$section/,/###/{/###/!p}" $output | sed -n "{/^$/!p}" | sed ':a;N;$!ba;s/\n/\\n/g')
|
||||
util::changelog::write_changelog "$TARGET_RELEASE" "$section" "$release_notes"
|
||||
done
|
||||
|
||||
rm -f $output
|
||||
echo "Successfully updated CHANGELOG for submodule."
|
||||
109
scripts/util/changelog.sh
Executable file
109
scripts/util/changelog.sh
Executable file
@ -0,0 +1,109 @@
|
||||
#!/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.
|
||||
|
||||
changelog="$(git rev-parse --show-toplevel)/CHANGELOG.md"
|
||||
|
||||
function util::changelog::has_release {
|
||||
local release=$1
|
||||
return $(grep -q "^# $release$" $changelog)
|
||||
}
|
||||
|
||||
# find_release_start returns the number of the first line of the given release
|
||||
function util::changelog::find_release_start {
|
||||
local release=$1
|
||||
echo $(grep -n "^# $release$" $changelog | head -1 | cut -d: -f1)
|
||||
}
|
||||
|
||||
# find_release_end returns the number of the last line of the given release
|
||||
function util::changelog::find_release_end {
|
||||
local release=$1
|
||||
|
||||
local release_start=$(util::changelog::find_release_start $release)
|
||||
local next_release_index=0
|
||||
local releases=($(grep -n "^# " $changelog | cut -d: -f1))
|
||||
for i in "${!releases[@]}"; do
|
||||
if [[ "${releases[$i]}" = "$release_start" ]]; then
|
||||
next_release_index=$((i+1))
|
||||
break
|
||||
fi
|
||||
done
|
||||
# return the line before the next release
|
||||
echo $((${releases[${next_release_index}]}-1))
|
||||
}
|
||||
|
||||
# has_section returns if the given section exists between start and end
|
||||
function util::changelog::has_section_in_range {
|
||||
local section="$1"
|
||||
local start=$2
|
||||
local end=$3
|
||||
|
||||
local lines=($(grep -n "$section" "$changelog" | cut -d: -f1))
|
||||
for i in "${!lines[@]}"; do
|
||||
if [[ ${lines[$i]} -ge $start && ${lines[$i]} -le $end ]]; then
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
# find_section returns the number of the first line of the given section
|
||||
function util::changelog::find_section_in_range {
|
||||
local section="$1"
|
||||
local start=$2
|
||||
local end=$3
|
||||
|
||||
local line="0"
|
||||
local lines=($(grep -n "$section" "$changelog" | cut -d: -f1))
|
||||
for i in "${!lines[@]}"; do
|
||||
if [[ ${lines[$i]} -ge $start && ${lines[$i]} -le $end ]]; then
|
||||
line=${lines[$i]}
|
||||
break
|
||||
fi
|
||||
done
|
||||
echo $line
|
||||
}
|
||||
|
||||
# write_changelog writes release_notes to section in target_release
|
||||
function util::changelog::write_changelog {
|
||||
local target_release="$1"
|
||||
local section="$2"
|
||||
local release_notes="$3"
|
||||
|
||||
# find the place in the changelog that we want to edit
|
||||
local line_to_edit="1"
|
||||
if util::changelog::has_release $target_release; then
|
||||
# the target release exists
|
||||
release_first_line=$(util::changelog::find_release_start $target_release)
|
||||
release_last_line=$(util::changelog::find_release_end $target_release)
|
||||
if util::changelog::has_section_in_range "$section" "$release_first_line" "$release_last_line"; then
|
||||
# prepend to existing section
|
||||
line_to_edit=$(($(util::changelog::find_section_in_range "$section" "$release_first_line" "$release_last_line")+1))
|
||||
else
|
||||
# add a new section; plus 4 so that the section is placed below "Kubernetes API Version"
|
||||
line_to_edit=$(($(util::changelog::find_release_start $target_release)+4))
|
||||
release_notes="$section\n$release_notes\n"
|
||||
fi
|
||||
else
|
||||
# add a new release
|
||||
release_notes="# $target_release\n\nKubernetes API Version: To Be Updated\n\n$section\n$release_notes\n"
|
||||
fi
|
||||
|
||||
echo "Writing the following release notes to CHANGELOG line $line_to_edit:"
|
||||
echo -e $release_notes
|
||||
|
||||
# update changelog
|
||||
sed -i "${line_to_edit}i${release_notes}" $changelog
|
||||
}
|
||||
3
setup.py
3
setup.py
@ -61,7 +61,7 @@ setup(
|
||||
'kubernetes.watch', 'kubernetes.client.api',
|
||||
'kubernetes.stream', 'kubernetes.client.models',
|
||||
'kubernetes.utils', 'kubernetes.client.apis',
|
||||
'kubernetes.dynamic'],
|
||||
'kubernetes.dynamic', 'kubernetes.leaderelection'],
|
||||
include_package_data=True,
|
||||
long_description="Python client for kubernetes http://kubernetes.io/",
|
||||
classifiers=[
|
||||
@ -79,5 +79,6 @@ setup(
|
||||
"Programming Language :: Python :: 3.6",
|
||||
"Programming Language :: Python :: 3.7",
|
||||
"Programming Language :: Python :: 3.8",
|
||||
"Programming Language :: Python :: 3.9",
|
||||
],
|
||||
)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user