Merge branch 'master' into patch-1

This commit is contained in:
Yacov Malen 2019-10-23 11:10:48 +03:00 committed by GitHub
commit be0a752240
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
63 changed files with 1245 additions and 705 deletions

20
.github/ISSUE_TEMPLATE/bug.md vendored Normal file
View File

@ -0,0 +1,20 @@
---
name: Bug report
about: Share about things that are not working as expected
labels: kind/bug
---
**What happened (please include outputs or screenshots)**:
**What you expected to happen**:
**How to reproduce it (as minimally and precisely as possible)**:
**Anything else we need to know?**:
**Environment**:
- Kubernetes version (`kubectl version`):
- OS (e.g., MacOS 10.13.6):
- Python version (`python --version`)
- Python client version (`pip list | grep kubernetes`)

10
.github/ISSUE_TEMPLATE/documentation.md vendored Normal file
View File

@ -0,0 +1,10 @@
---
name: Documentation
about: Report any mistakes or missing information from the documentation or the examples
labels: kind/documentation
---
**Link to the issue (please include a link to the specific documentation or example)**:
**Description of the issue (please include outputs or screenshots if possible)**:

10
.github/ISSUE_TEMPLATE/feature.md vendored Normal file
View File

@ -0,0 +1,10 @@
---
name: Feature request
about: Suggest a new feature for the project
labels: kind/feature
---
**What is the feature and why do you need it**:
**Describe the solution you'd like to see**:

4
.gitignore vendored
View File

@ -67,3 +67,7 @@ target/
.idea/*
*.iml
.vscode
# created by sphinx documentation build
doc/source/README.md
doc/_build

View File

@ -1,7 +1,6 @@
# ref: https://docs.travis-ci.com/user/languages/python
language: python
dist: xenial
sudo: true
services:
- docker
@ -13,12 +12,10 @@ matrix:
env: TOXENV=py27-functional
- python: 2.7
env: TOXENV=update-pycodestyle
- python: 2.7
- python: 3.7
env: TOXENV=docs
- python: 2.7
env: TOXENV=coverage,codecov
- python: 3.4
env: TOXENV=py34
- python: 3.5
env: TOXENV=py35
- python: 3.5
@ -31,6 +28,10 @@ matrix:
env: TOXENV=py37
- python: 3.7
env: TOXENV=py37-functional
- python: 3.8
env: TOXENV=py38
- python: 3.8
env: TOXENV=py38-functional
install:
- pip install tox

View File

@ -25,6 +25,7 @@
- Introduce RuntimeClass to NodeV1alpha1Api and NodeV1beta1Api [kubernetes/kubernetes#74433](https://github.com/kubernetes/kubernetes/pull/74433)
- Graduate PriorityClass API to GA SchedulingV1Api [kubernetes/kubernetes#73555](https://github.com/kubernetes/kubernetes/pull/73555)
- Introduce CSINodeInfo and CSIDriver to StorageV1beta1Api [kubernetes/kubernetes#74283](https://github.com/kubernetes/kubernetes/pull/74283)
- The alpha Initializers feature, `admissionregistration.k8s.io/v1alpha1` API version, `Initializers` admission plugin, and use of the `metadata.initializers` API field have been removed. Discontinue use of the alpha feature and delete any existing `InitializerConfiguration` API objects before upgrading. The `metadata.initializers` field will be removed in a future release. The parameter `include_uninitialized` has been removed. [kubernetes/kubernetes#72972](https://github.com/kubernetes/kubernetes/pull/72972)
# v9.0.0
**Bug Fix:**

9
OWNERS
View File

@ -1,8 +1,11 @@
# See the OWNERS docs at https://go.k8s.io/owners
approvers:
- mbohlool
- roycaihw
- yliaog
emeritus_approvers:
- caesarxuchao
- lavalamp
- yliaog
- roycaihw
- mbohlool
reviewers:
- micw523

View File

@ -25,7 +25,7 @@ From [PyPi](https://pypi.python.org/pypi/kubernetes/) directly:
pip install kubernetes
```
## Example
## Examples
list all pods:
@ -68,7 +68,7 @@ More examples can be found in [examples](examples/) folder. To run examples, run
python -m examples.example1
```
(replace example1 with the example base filename)
(replace example1 with one of the filenames in the examples folder)
## Documentation
@ -77,24 +77,20 @@ All APIs and Models' documentation can be found at the [Generated client's READM
## Compatibility
`client-python` follows [semver](http://semver.org/), so until the major version of
client-python gets increased, your code will continue to work with explicitly
client-python gets increased, your code will continue to work with explicitly
supported versions of Kubernetes clusters.
#### Compatibility matrix
| | Kubernetes 1.5 | Kubernetes 1.6 | Kubernetes 1.7 | Kubernetes 1.8 | Kubernetes 1.9 | Kubernetes 1.10 | Kubernetes 1.11 | Kubernetes 1.12 | Kubernetes 1.13 | Kubernetes 1.14 |
|--------------------|----------------|----------------|----------------|----------------|----------------|-----------------|-----------------|-----------------|-----------------|-----------------|
| client-python 1.0 | ✓ | - | - |- |- |- |- |- |- |- |
| client-python 2.0 | + | ✓ | - |- |- |- |- |- |- |- |
| client-python 3.0 | + | + | ✓ |- |- |- |- |- |- |- |
| client-python 4.0 | + | + | + |✓ |- |- |- |- |- |- |
| client-python 5.0 | + | + | + |+ |✓ |- |- |- |- |- |
| client-python 6.0 | + | + | + |+ |+ |✓ |- |- |- |- |
| client-python 7.0 | + | + | + |+ |+ |+ |✓ |- |- |- |
| client-python 8.0 | + | + | + |+ |+ |+ |+ |✓ |- |- |
| client-python 9.0 | + | + | + |+ |+ |+ |+ |+ |✓ |- |
| client-python 10.0 | + | + | + |+ |+ |+ |+ |+ |+ |✓ |
| client-python HEAD | + | + | + |+ |+ |+ |+ |+ |+ |✓ |
| | Kubernetes 1.9 | Kubernetes 1.10 | Kubernetes 1.11 | Kubernetes 1.12 | Kubernetes 1.13 | Kubernetes 1.14 |
|--------------------|----------------|-----------------|-----------------|-----------------|-----------------|-----------------|
| client-python 5.0 |✓ |- |- |- |- |- |
| client-python 6.0 |+ |✓ |- |- |- |- |
| client-python 7.0 |+ |+ |✓ |- |- |- |
| client-python 8.0 |+ |+ |+ |✓ |- |- |
| client-python 9.0 |+ |+ |+ |+ |✓ |- |
| client-python 10.0 |+ |+ |+ |+ |+ |✓ |
| client-python HEAD |+ |+ |+ |+ |+ |✓ |
Key:
@ -110,14 +106,6 @@ between client-python versions.
| Client version | Canonical source for OpenAPI spec | Maintenance status |
|-----------------|--------------------------------------|-------------------------------|
| 1.0 Alpha/Beta | Kubernetes main repo, 1.5 branch | ✗ |
| 1.0.x | Kubernetes main repo, 1.5 branch | ✗ |
| 2.0 Alpha/Beta | Kubernetes main repo, 1.6 branch | ✗ |
| 2.0.x | Kubernetes main repo, 1.6 branch | ✗ |
| 3.0 Alpha/Beta | Kubernetes main repo, 1.7 branch | ✗ |
| 3.0 | Kubernetes main repo, 1.7 branch | ✗ |
| 4.0 Alpha/Beta | Kubernetes main repo, 1.8 branch | ✗ |
| 4.0 | Kubernetes main repo, 1.8 branch | ✗ |
| 5.0 Alpha/Beta | Kubernetes main repo, 1.9 branch | ✗ |
| 5.0 | Kubernetes main repo, 1.9 branch | ✗ |
| 6.0 Alpha/Beta | Kubernetes main repo, 1.10 branch | ✗ |
@ -142,18 +130,12 @@ Note: There would be no maintenance for alpha/beta releases except the latest on
## Community, Support, Discussion
If you have any problem on using the package or any suggestions, please start with reaching the [Kubernetes clients slack channel](https://kubernetes.slack.com/messages/C76GB48RK/), or filing an [issue](https://github.com/kubernetes-client/python/issues) to let us know. You can also reach the maintainers of this project at [SIG API Machinery](https://github.com/kubernetes/community/tree/master/sig-api-machinery).
If you have any problem on using the package or any suggestions, please start with reaching the [Kubernetes clients slack channel](https://kubernetes.slack.com/messages/C76GB48RK/), or filing an [issue](https://github.com/kubernetes-client/python/issues) to let us know. You can also reach the maintainers of this project at [SIG API Machinery](https://github.com/kubernetes/community/tree/master/sig-api-machinery), where this project falls under.
### Code of Conduct
Participation in the Kubernetes community is governed by the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/master/code-of-conduct.md).
## Kubernetes Incubator
This is a [Kubernetes Incubator project](https://github.com/kubernetes/community/blob/master/incubator.md).
* [SIG: sig-api-machinery](https://github.com/kubernetes/community/tree/master/sig-api-machinery)
## Troubleshooting
### SSLError on macOS
@ -184,9 +166,9 @@ Specifically check `ipaddress` and `urllib3` package versions to make sure they
Starting from 4.0 release, we do not support directly calling exec or attach calls. you should use stream module to call them. so instead
of `resp = api.connect_get_namespaced_pod_exec(name, ...` you should call `resp = stream(api.connect_get_namespaced_pod_exec, name, ...`.
See more at [exec example](examples/exec.py).
Using Stream will overwrite the requests protocol in _core_v1_api.CoreV1Api()_
This will cause a failure in non-exec/attach calls. If you reuse your api client object, you will need to
recreate it between api calls that use _stream_ and other api calls.
See more at [exec example](examples/pod_exec.py).

View File

@ -3,7 +3,11 @@
The Kubernetes Python client is released on an as-needed basis. The process is as follows:
1. An issue is proposing a new release with a changelog since the last release
1. Update the support matrix in README.md removing the oldest version and adding the
proposed release.
1. All [OWNERS](OWNERS) must LGTM this release
1. An OWNER runs `git tag -s $VERSION` and inserts the changelog and pushes the tag with `git push $VERSION`
1. An OWNER runs `git tag -s $VERSION` and inserts the changelog and pushes
the tag with `git push $VERSION`
1. The release issue is closed
1. An announcement email is sent to `kubernetes-dev@googlegroups.com` with the subject `[ANNOUNCE] kubernetes-python-client $VERSION is released`
1. An announcement email is sent to `kubernetes-dev@googlegroups.com`
with the subject `[ANNOUNCE] kubernetes-python-client $VERSION is released`

21
doc/Makefile Normal file
View File

@ -0,0 +1,21 @@
# Minimal makefile for Sphinx documentation
#
# You can set these variables from the command line.
SPHINXOPTS = -c source
SPHINXBUILD = sphinx-build
SPHINXPROJ = kubernetes-python
SOURCEDIR = source
BUILDDIR = build
# Put it first so that "make" without argument is like "make help".
help:
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
.PHONY: help Makefile
# Catch-all target: route all unknown targets to Sphinx using the new
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
html:
$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
@echo "\nDocs rendered successfully, open _/build/html/index.html to view"

11
doc/README.md Normal file
View File

@ -0,0 +1,11 @@
Building the documentation
==========================
Install the test requirements with:
```
$ pip install -r ../test-requirements.txt
```
Use `make html` to build the docs in html format.

View File

@ -0,0 +1,2 @@
recommonmark
sphinx_markdown_tables

View File

@ -13,22 +13,37 @@
# limitations under the License.
import os
import re
import shutil
import sys
from recommonmark.parser import CommonMarkParser
from recommonmark.transform import AutoStructify
# Work around https://github.com/readthedocs/recommonmark/issues/152
new_readme = []
with open("../../README.md", "r") as r:
lines = r.readlines()
for l in lines:
nl = re.sub("\[!\[[\w\s]+\]\(", "[![](", l)
new_readme.append(nl)
with open("README.md", "w") as n:
n.writelines(new_readme)
# apparently index.rst can't search for markdown not in the same directory
shutil.copy("../../CONTRIBUTING.md", ".")
sys.path.insert(0, os.path.abspath('../..'))
# -- General configuration ----------------------------------------------------
source_parsers = {
'.md': CommonMarkParser,
}
source_suffix = ['.rst', '.md']
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
extensions = [
'sphinx_markdown_tables',
'recommonmark',
'sphinx.ext.autodoc',
#'sphinx.ext.intersphinx',
]
@ -80,3 +95,10 @@ latex_documents = [
# Example configuration for intersphinx: refer to the Python standard library.
#intersphinx_mapping = {'http://docs.python.org/': None}
def setup(app):
app.add_config_value('recommonmark_config', {
'auto_toc_tree_section': 'Contents',
'enable_eval_rst': True,
}, True)
app.add_transform(AutoStructify)

View File

@ -1,4 +0,0 @@
============
Contributing
============
.. include:: ../../CONTRIBUTING.md

View File

@ -11,11 +11,12 @@ Contents:
.. toctree::
:maxdepth: 2
readme
README <README.md>
installation
usage
examples
modules
contributing
contributing <CONTRIBUTING.md>
Indices and tables
==================

View File

@ -1,4 +0,0 @@
======
Readme
======
.. include:: ../../README.md

View File

@ -2,6 +2,19 @@
Usage
========
To use kubernetes-python-client in a project::
The directory ``examples`` contains a few examples on how to use the client.
import kubernetes
Deployments
-----------
Here is a simple usage of creating a deployment from a yaml file:
.. literalinclude:: ../../examples/create_deployment.py
The following example demostrates how to create, update and delete deployments
without the need to read a file from the disk:
.. literalinclude:: ../../examples/deployment_examples.py

17
examples/README.md Normal file
View File

@ -0,0 +1,17 @@
# Python Client Examples
This directory contains various examples of how to use the Python client.
Please read the description at the top of each example for more information
about what the script does and any prequisites. Most scripts also include
comments throughout the code.
## Setup
These scripts require Python 2.7 or 3.5+ and the Kubernetes client which can be
installed following the directions
[here](https://github.com/kubernetes-client/python#installation).
## Contributions
If you find a problem please file an
[issue](https://github.com/kubernetes-client/python/issues).

View File

@ -12,4 +12,4 @@
# See the License for the specific language governing permissions and
# limitations under the License.
# Empty init file to make examples folder a python module.
# Empty init file to make examples folder a python module

View File

@ -12,6 +12,11 @@
# See the License for the specific language governing permissions and
# limitations under the License.
"""
Reads the list of available API versions and prints them. Similar to running
`kubectl api-versions`.
"""
from kubernetes import client, config
@ -22,7 +27,7 @@ def main():
config.load_kube_config()
print("Supported APIs (* is preferred version):")
print("%-20s %s" %
print("%-40s %s" %
("core", ",".join(client.CoreApi().get_api_versions().versions)))
for api in client.ApisApi().get_api_versions().groups:
versions = []

View File

@ -1,33 +0,0 @@
# Copyright 2018 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.
from os import path
from kubernetes import client, config, utils
def main():
# Configs can be set in Configuration class directly or using helper
# utility. If no argument provided, the config will be loaded from
# default location.
config.load_kube_config()
k8s_client = client.ApiClient()
utils.create_from_yaml(k8s_client, "nginx-deployment.yaml")
k8s_api = client.ExtensionsV1beta1Api(k8s_client)
deps = k8s_api.read_namespaced_deployment("nginx-deployment", "default")
print("Deployment {0} created".format(deps.metadata.name))
if __name__ == '__main__':
main()

View File

@ -1,40 +0,0 @@
## Creating a Third Party Resource
```
from __future__ import print_function
from pprint import pprint
import kubernetes
from kubernetes import config
from kubernetes.rest import ApiException
config.load_kube_config()
api_instance = kubernetes.ThirdPartyResources()
namespace = 'default'
resource = 'repos'
fqdn = 'git.k8s.com'
body = {}
body['apiVersion'] = "git.k8s.com/v1"
body['kind'] = "RePo"
body['metadata'] = {}
body['metadata']['name'] = "blog-repo"
body['repo'] = "github.com/user/my-blog"
body['username'] = "username"
body['password'] = "password"
body['branch'] = "branch"
try:
# Create a Resource
api_response = api_instance.apis_fqdn_v1_namespaces_namespace_resource_post(
namespace, fqdn, resource, body)
pprint(api_response)
except ApiException as e:
print(
"Exception when calling DefaultApi->apis_fqdn_v1_namespaces_namespace_resource_post: %s\n" %
e)
```

94
examples/custom_object.py Normal file
View File

@ -0,0 +1,94 @@
# Copyright 2019 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 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:
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
scope: Namespaced
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()
# it's my custom resource defined as Dict
my_resource = {
"apiVersion": "stable.example.com/v1",
"kind": "CronTab",
"metadata": {"name": "my-new-cron-object"},
"cronSpec": "* * * * */5",
"image": "my-awesome-cron-image",
}
# create the resource
api.create_namespaced_custom_object(
group="stable.example.com",
version="v1",
namespace="default",
plural="crontabs",
body=my_resource,
)
print("Resource created")
# get the resource and print out data
resource = api.get_namespaced_custom_object(
group="stable.example.com",
version="v1",
name="my-new-cron-object",
namespace="default",
plural="crontabs",
)
print("Resource details:")
pprint(resource)
# delete it
api.delete_namespaced_custom_object(
group="stable.example.com",
version="v1",
name="my-new-cron-object",
namespace="default",
plural="crontabs",
body=client.V1DeleteOptions(),
)
print("Resource deleted")
if __name__ == "__main__":
main()

View File

@ -27,10 +27,10 @@ def main():
with open(path.join(path.dirname(__file__), "nginx-deployment.yaml")) as f:
dep = yaml.safe_load(f)
k8s_beta = client.ExtensionsV1beta1Api()
resp = k8s_beta.create_namespaced_deployment(
k8s_apps_v1 = client.AppsV1Api()
resp = k8s_apps_v1.create_namespaced_deployment(
body=dep, namespace="default")
print("Deployment created. status='%s'" % str(resp.status))
print("Deployment created. status='%s'" % resp.metadata.name)
if __name__ == '__main__':

View File

@ -12,9 +12,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from os import path
import yaml
"""
Creates, updates, and deletes a deployment using AppsV1Api.
"""
from kubernetes import client, config
@ -25,19 +25,20 @@ def create_deployment_object():
# Configureate Pod template container
container = client.V1Container(
name="nginx",
image="nginx:1.7.9",
image="nginx:1.15.4",
ports=[client.V1ContainerPort(container_port=80)])
# Create and configurate a spec section
template = client.V1PodTemplateSpec(
metadata=client.V1ObjectMeta(labels={"app": "nginx"}),
spec=client.V1PodSpec(containers=[container]))
# Create the specification of deployment
spec = client.ExtensionsV1beta1DeploymentSpec(
spec = client.V1DeploymentSpec(
replicas=3,
template=template)
template=template,
selector={'matchLabels': {'app': 'nginx'}})
# Instantiate the deployment object
deployment = client.ExtensionsV1beta1Deployment(
api_version="extensions/v1beta1",
deployment = client.V1Deployment(
api_version="apps/v1",
kind="Deployment",
metadata=client.V1ObjectMeta(name=DEPLOYMENT_NAME),
spec=spec)
@ -55,7 +56,7 @@ def create_deployment(api_instance, deployment):
def update_deployment(api_instance, deployment):
# Update container image
deployment.spec.template.spec.containers[0].image = "nginx:1.9.1"
deployment.spec.template.spec.containers[0].image = "nginx:1.16.0"
# Update the deployment
api_response = api_instance.patch_namespaced_deployment(
name=DEPLOYMENT_NAME,
@ -80,16 +81,16 @@ def main():
# utility. If no argument provided, the config will be loaded from
# default location.
config.load_kube_config()
extensions_v1beta1 = client.ExtensionsV1beta1Api()
apps_v1 = client.AppsV1Api()
# Create a deployment object with client-python API. The deployment we
# created is same as the `nginx-deployment.yaml` in the /examples folder.
deployment = create_deployment_object()
create_deployment(extensions_v1beta1, deployment)
create_deployment(apps_v1, deployment)
update_deployment(extensions_v1beta1, deployment)
update_deployment(apps_v1, deployment)
delete_deployment(extensions_v1beta1)
delete_deployment(apps_v1)
if __name__ == '__main__':

View File

@ -1,97 +0,0 @@
import time
from kubernetes import config
from kubernetes.client import Configuration
from kubernetes.client.apis import core_v1_api
from kubernetes.client.rest import ApiException
from kubernetes.stream import stream
config.load_kube_config()
c = Configuration()
c.assert_hostname = False
Configuration.set_default(c)
api = core_v1_api.CoreV1Api()
name = 'busybox-test'
resp = None
try:
resp = api.read_namespaced_pod(name=name,
namespace='default')
except ApiException as e:
if e.status != 404:
print("Unknown error: %s" % e)
exit(1)
if not resp:
print("Pod %s does not exist. Creating it..." % name)
pod_manifest = {
'apiVersion': 'v1',
'kind': 'Pod',
'metadata': {
'name': name
},
'spec': {
'containers': [{
'image': 'busybox',
'name': 'sleep',
"args": [
"/bin/sh",
"-c",
"while true;do date;sleep 5; done"
]
}]
}
}
resp = api.create_namespaced_pod(body=pod_manifest,
namespace='default')
while True:
resp = api.read_namespaced_pod(name=name,
namespace='default')
if resp.status.phase != 'Pending':
break
time.sleep(1)
print("Done.")
# calling exec and wait for response.
exec_command = [
'/bin/sh',
'-c',
'echo This message goes to stderr >&2; echo This message goes to stdout']
resp = stream(api.connect_get_namespaced_pod_exec, name, 'default',
command=exec_command,
stderr=True, stdin=False,
stdout=True, tty=False)
print("Response: " + resp)
# Calling exec interactively.
exec_command = ['/bin/sh']
resp = stream(api.connect_get_namespaced_pod_exec, name, 'default',
command=exec_command,
stderr=True, stdin=True,
stdout=True, tty=False,
_preload_content=False)
commands = [
"echo test1",
"echo \"This message goes to stderr\" >&2",
]
while resp.is_open():
resp.update(timeout=1)
if resp.peek_stdout():
print("STDOUT: %s" % resp.read_stdout())
if resp.peek_stderr():
print("STDERR: %s" % resp.read_stderr())
if commands:
c = commands.pop(0)
print("Running command... %s\n" % c)
resp.write_stdin(c + "\n")
else:
break
resp.write_stdin("date\n")
sdate = resp.readline_stdout(timeout=3)
print("Server date command returns: %s" % sdate)
resp.write_stdin("whoami\n")
user = resp.readline_stdout(timeout=3)
print("Server user is: %s" % user)
resp.close()

View File

@ -12,47 +12,46 @@
# See the License for the specific language governing permissions and
# limitations under the License.
# Simple example to show loading config from the cluster
#
# It works only from a pod. You can start an image with Python
# (for example python:latest), exec into the pod, install the library,
# then try out this example.
#
# If you get 403 errors from API server you will have to configure
# RBAC to add the permission to list pods.
#
# ---
# kind: ClusterRole
# apiVersion: rbac.authorization.k8s.io/v1
# metadata:
# name: pods-list
# rules:
# - apiGroups: [""]
# resources: ["pods"]
# verbs: ["list"]
# ---
# kind: ClusterRoleBinding
# apiVersion: rbac.authorization.k8s.io/v1
# metadata:
# name: pods-list
# subjects:
# - kind: ServiceAccount
# name: default
# namespace: default
# roleRef:
# kind: ClusterRole
# name: pods-list
# apiGroup: rbac.authorization.k8s.io
# ---
#
# Doc: https://kubernetes.io/docs/reference/access-authn-authz/rbac/
"""
Shows how to load a Kubernetes config from within a cluster. This script
must be run within a pod. You can start a pod with a Python image (for
example, `python:latest`), exec into the pod, install the library, then run
this example.
If you get 403 errors from the API server you will have to configure RBAC to
add permission to list pods by applying the following manifest:
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: pods-list
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["list"]
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: pods-list
subjects:
- kind: ServiceAccount
name: default
namespace: default
roleRef:
kind: ClusterRole
name: pods-list
apiGroup: rbac.authorization.k8s.io
Documentation: https://kubernetes.io/docs/reference/access-authn-authz/rbac/
"""
from kubernetes import client, config
def main():
# it works only if this script is run by K8s as a POD
config.load_incluster_config()
v1 = client.CoreV1Api()

115
examples/ingress_create.py Normal file
View File

@ -0,0 +1,115 @@
# Copyright 2019 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.
"""
Creates deployment, service, and ingress objects. The ingress allows external
network access to the cluster.
"""
from kubernetes import client, config
def create_deployment(apps_v1_api):
container = client.V1Container(
name="deployment",
image="gcr.io/google-appengine/fluentd-logger",
image_pull_policy="Never",
ports=[client.V1ContainerPort(container_port=5678)],
)
# Template
template = client.V1PodTemplateSpec(
metadata=client.V1ObjectMeta(labels={"app": "deployment"}),
spec=client.V1PodSpec(containers=[container]))
# Spec
spec = client.V1DeploymentSpec(
replicas=1,
template=template)
# Deployment
deployment = client.V1Deployment(
api_version="apps/v1",
kind="Deployment",
metadata=client.V1ObjectMeta(name="deployment"),
spec=spec)
# Creation of the Deployment in specified namespace
# (Can replace "default" with a namespace you may have created)
apps_v1_api.create_namespaced_deployment(
namespace="default", body=deployment
)
def create_service():
core_v1_api = client.CoreV1Api()
body = client.V1Service(
api_version="v1",
kind="Service",
metadata=client.V1ObjectMeta(
name="service-example"
),
spec=client.V1ServiceSpec(
selector={"app": "deployment"},
ports=[client.V1ServicePort(
port=5678,
target_port=5678
)]
)
)
# Creation of the Deployment in specified namespace
# (Can replace "default" with a namespace you may have created)
core_v1_api.create_namespaced_service(namespace="default", body=body)
def create_ingress(networking_v1_beta1_api):
body = client.NetworkingV1beta1Ingress(
api_version="networking.k8s.io/v1beta1",
kind="Ingress",
metadata=client.V1ObjectMeta(name="ingress-example", annotations={
"nginx.ingress.kubernetes.io/rewrite-target": "/"
}),
spec=client.NetworkingV1beta1IngressSpec(
rules=[client.NetworkingV1beta1IngressRule(
host="example.com",
http=client.NetworkingV1beta1HTTPIngressRuleValue(
paths=[client.NetworkingV1beta1HTTPIngressPath(
path="/",
backend=client.NetworkingV1beta1IngressBackend(
service_port=5678,
service_name="service-example")
)]
)
)
]
)
)
# Creation of the Deployment in specified namespace
# (Can replace "default" with a namespace you may have created)
networking_v1_beta1_api.create_namespaced_ingress(
namespace="default",
body=body
)
def main():
# Fetching and loading local Kubernetes Information
config.load_kube_config()
apps_v1_api = client.AppsV1Api()
networking_v1_beta1_api = client.NetworkingV1beta1Api()
create_deployment(apps_v1_api)
create_service()
create_ingress(networking_v1_beta1_api)
if __name__ == "__main__":
main()

97
examples/job_crud.py Normal file
View File

@ -0,0 +1,97 @@
# Copyright 2016 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.
"""
Creates, updates, and deletes a job object.
"""
from os import path
import yaml
from kubernetes import client, config
JOB_NAME = "pi"
def create_job_object():
# Configureate Pod template container
container = client.V1Container(
name="pi",
image="perl",
command=["perl", "-Mbignum=bpi", "-wle", "print bpi(2000)"])
# Create and configurate a spec section
template = client.V1PodTemplateSpec(
metadata=client.V1ObjectMeta(labels={"app": "pi"}),
spec=client.V1PodSpec(restart_policy="Never", containers=[container]))
# Create the specification of deployment
spec = client.V1JobSpec(
template=template,
backoff_limit=4)
# Instantiate the job object
job = client.V1Job(
api_version="batch/v1",
kind="Job",
metadata=client.V1ObjectMeta(name=JOB_NAME),
spec=spec)
return job
def create_job(api_instance, job):
api_response = api_instance.create_namespaced_job(
body=job,
namespace="default")
print("Job created. status='%s'" % str(api_response.status))
def update_job(api_instance, job):
# Update container image
job.spec.template.spec.containers[0].image = "perl"
api_response = api_instance.patch_namespaced_job(
name=JOB_NAME,
namespace="default",
body=job)
print("Job updated. status='%s'" % str(api_response.status))
def delete_job(api_instance):
api_response = api_instance.delete_namespaced_job(
name=JOB_NAME,
namespace="default",
body=client.V1DeleteOptions(
propagation_policy='Foreground',
grace_period_seconds=5))
print("Job deleted. status='%s'" % str(api_response.status))
def main():
# Configs can be set in Configuration class directly or using helper
# utility. If no argument provided, the config will be loaded from
# default location.
config.load_kube_config()
batch_v1 = client.BatchV1Api()
# Create a job object with client-python API. The job we
# created is same as the `pi-job.yaml` in the /examples folder.
job = create_job_object()
create_job(batch_v1, job)
update_job(batch_v1, job)
delete_job(batch_v1)
if __name__ == '__main__':
main()

View File

@ -12,14 +12,18 @@
# See the License for the specific language governing permissions and
# limitations under the License.
"""
Allows you to pick a context and then lists all pods in the chosen context.
Please install the pick library before running this example.
"""
from kubernetes import client, config
# install pick using "pip install pick". It is not included
# as a dependency because it only used in examples
from pick import pick
from kubernetes.client import configuration
from pick import pick # install pick using `pip install pick`
def main():
contexts, active_context = config.list_kube_config_contexts()
if not contexts:
print("Cannot find any context in kube-config file.")

View File

@ -1,9 +1,14 @@
apiVersion: extensions/v1beta1
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
labels:
app: nginx
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
@ -11,7 +16,6 @@ spec:
spec:
containers:
- name: nginx
image: nginx:1.7.9
image: nginx:1.15.4
ports:
- containerPort: 80

View File

@ -12,19 +12,18 @@
# See the License for the specific language governing permissions and
# 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".
"""
from pprint import pprint
from kubernetes import client, config
def main():
"""
Change labels of the "minikube" node:
- Add label "foo" with value "bar". This will overwrite the "foo" label
if it already exists.
- Remove the label "baz" from the node.
"""
config.load_kube_config()
api_instance = client.CoreV1Api()

View File

@ -47,7 +47,7 @@
"outputs": [],
"source": [
"config.load_kube_config()\n",
"extension = client.ExtensionsV1beta1Api()"
"apps_api = client.AppsV1beta1Api()"
]
},
{
@ -70,7 +70,7 @@
},
"outputs": [],
"source": [
"deployment = client.ExtensionsV1beta1Deployment()"
"deployment = client.AppsV1beta1Deployment()"
]
},
{
@ -93,7 +93,7 @@
},
"outputs": [],
"source": [
"deployment.api_version = \"extensions/v1beta1\"\n",
"deployment.api_version = \"apps/v1beta1\"\n",
"deployment.kind = \"Deployment\"\n",
"deployment.metadata = client.V1ObjectMeta(name=\"nginx-deployment\")"
]
@ -118,7 +118,7 @@
},
"outputs": [],
"source": [
"spec = client.ExtensionsV1beta1DeploymentSpec()\n",
"spec = client.AppsV1beta1DeploymentSpec()\n",
"spec.replicas = 3"
]
},
@ -207,7 +207,7 @@
},
"outputs": [],
"source": [
"extension.create_namespaced_deployment(namespace=\"default\", body=deployment)"
"apps_api.create_namespaced_deployment(namespace=\"default\", body=deployment)"
]
},
{
@ -253,7 +253,7 @@
},
"outputs": [],
"source": [
"extension.replace_namespaced_deployment(name=\"nginx-deployment\", namespace=\"default\", body=deployment)"
"apps_api.replace_namespaced_deployment(name=\"nginx-deployment\", namespace=\"default\", body=deployment)"
]
},
{
@ -277,10 +277,10 @@
},
"outputs": [],
"source": [
"rollback = client.ExtensionsV1beta1DeploymentRollback()\n",
"rollback.api_version = \"extensions/v1beta1\"\n",
"rollback = client.AppsV1beta1DeploymentRollback()\n",
"rollback.api_version = \"apps/v1beta1\"\n",
"rollback.kind = \"DeploymentRollback\"\n",
"rollback.rollback_to = client.ExtensionsV1beta1RollbackConfig()\n",
"rollback.rollback_to = client.AppsV1beta1RollbackConfig()\n",
"rollback.rollback_to.revision = 0\n",
"rollback.name = \"nginx-deployment\""
]

View File

@ -89,9 +89,9 @@
},
"outputs": [],
"source": [
"api_instance = client.ExtensionsV1beta1Api()\n",
"dep = client.ExtensionsV1beta1Deployment()\n",
"spec = client.ExtensionsV1beta1DeploymentSpec()"
"api_instance = client.AppsV1beta1Api()\n",
"dep = client.AppsV1beta1Deployment()\n",
"spec = client.AppsV1beta1DeploymentSpec()"
]
},
{

View File

@ -1,104 +0,0 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": 7,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"from kubernetes import client, config"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"config.load_incluster_config()"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"v1=client.CoreV1Api()"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"default\n",
"kube-system\n",
"kubeless\n"
]
}
],
"source": [
"for ns in v1.list_namespace().items:\n",
" print ns.metadata.name"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": []
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 2",
"language": "python",
"name": "python2"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 2
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython2",
"version": "2.7.12"
}
},
"nbformat": 4,
"nbformat_minor": 1
}

View File

@ -1,129 +0,0 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {
"deletable": true,
"editable": true
},
"source": [
"How to watch changes to an object\n",
"==================\n",
"\n",
"In this notebook, we learn how kubernetes API resource Watch endpoint is used to observe resource changes. It can be used to get information about changes to any kubernetes object."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": true,
"deletable": true,
"editable": true
},
"outputs": [],
"source": [
"from kubernetes import client, config, watch"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Load config from default location."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"config.load_kube_config()"
]
},
{
"cell_type": "markdown",
"metadata": {
"deletable": true,
"editable": true
},
"source": [
"### Create API instance"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": true,
"deletable": true,
"editable": true
},
"outputs": [],
"source": [
"api_instance = client.CoreV1Api()"
]
},
{
"cell_type": "markdown",
"metadata": {
"deletable": true,
"editable": true
},
"source": [
"### Run a Watch on the Pods endpoint. \n",
"Watch would be executed and produce output about changes to any Pod. After running the cell below, You can test this by running the Pod notebook [create_pod.ipynb](create_pod.ipynb) and observing the additional output here. You can stop the cell from running by restarting the kernel."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false,
"deletable": true,
"editable": true
},
"outputs": [],
"source": [
"w = watch.Watch()\n",
"for event in w.stream(api_instance.list_pod_for_all_namespaces):\n",
" print(\"Event: %s %s %s\" % (event['type'],event['object'].kind, event['object'].metadata.name))"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": true,
"deletable": true,
"editable": true
},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 2",
"language": "python",
"name": "python2"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 2
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython2",
"version": "2.7.6"
}
},
"nbformat": 4,
"nbformat_minor": 2
}

View File

@ -12,6 +12,10 @@
# See the License for the specific language governing permissions and
# limitations under the License.
"""
Shows how to load a Kubernetes config from outside of the cluster.
"""
from kubernetes import client, config

13
examples/pi-job.yaml Normal file
View File

@ -0,0 +1,13 @@
apiVersion: batch/v1
kind: Job
metadata:
name: pi
spec:
template:
spec:
containers:
- name: pi
image: perl
command: ["perl", "-Mbignum=bpi", "-wle", "print bpi(2000)"]
restartPolicy: Never
backoffLimit: 4

View File

@ -12,11 +12,15 @@
# See the License for the specific language governing permissions and
# limitations under the License.
"""
Allows you to pick a context and then lists all pods in the chosen context.
Please install the pick library before running this example.
"""
from kubernetes import client, config
from kubernetes.client import configuration
# install pick using "pip install pick". It is not included
# as a dependency because it only used in examples
from pick import pick
from pick import pick # install pick using `pip install pick`
def main():
@ -32,7 +36,7 @@ def main():
# utility
config.load_kube_config(context=option)
print("Active host is %s" % configuration.host)
print("Active host is %s" % configuration.Configuration().host)
v1 = client.CoreV1Api()
print("Listing pods with their IPs:")

View File

@ -0,0 +1,54 @@
# Copyright 2016 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.
"""
Allows you to pick a context and then lists all pods in the chosen context. A
context includes a cluster, a user, and a namespace.
Please install the pick library before running this example.
"""
from kubernetes import client, config
from kubernetes.client import configuration
from pick import pick # install pick using `pip install pick`
def main():
contexts, active_context = config.list_kube_config_contexts()
if not contexts:
print("Cannot find any context in kube-config file.")
return
contexts = [context['name'] for context in contexts]
active_index = contexts.index(active_context['name'])
option, _ = pick(contexts, title="Pick the context to load",
default_index=active_index)
# Configs can be set in Configuration class directly or using helper
# utility
config.load_kube_config(context=option)
print("Active host is %s" % configuration.Configuration().host)
v1 = client.CoreV1Api()
print("Listing pods with their IPs:")
ret = v1.list_pod_for_all_namespaces(watch=False)
for item in ret.items:
print(
"%s\t%s\t%s" %
(item.status.pod_ip,
item.metadata.namespace,
item.metadata.name))
if __name__ == '__main__':
main()

129
examples/pod_exec.py Normal file
View File

@ -0,0 +1,129 @@
# Copyright 2019 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.
"""
Shows the functionality of exec using a Busybox container.
"""
import time
from kubernetes import config
from kubernetes.client import Configuration
from kubernetes.client.apis import core_v1_api
from kubernetes.client.rest import ApiException
from kubernetes.stream import stream
def exec_commands(api_instance):
name = 'busybox-test'
resp = None
try:
resp = api_instance.read_namespaced_pod(name=name,
namespace='default')
except ApiException as e:
if e.status != 404:
print("Unknown error: %s" % e)
exit(1)
if not resp:
print("Pod %s does not exist. Creating it..." % name)
pod_manifest = {
'apiVersion': 'v1',
'kind': 'Pod',
'metadata': {
'name': name
},
'spec': {
'containers': [{
'image': 'busybox',
'name': 'sleep',
"args": [
"/bin/sh",
"-c",
"while true;do date;sleep 5; done"
]
}]
}
}
resp = api_instance.create_namespaced_pod(body=pod_manifest,
namespace='default')
while True:
resp = api_instance.read_namespaced_pod(name=name,
namespace='default')
if resp.status.phase != 'Pending':
break
time.sleep(1)
print("Done.")
# Calling exec and waiting for response
exec_command = [
'/bin/sh',
'-c',
'echo This message goes to stderr; echo This message goes to stdout']
resp = stream(api_instance.connect_get_namespaced_pod_exec,
name,
'default',
command=exec_command,
stderr=True, stdin=False,
stdout=True, tty=False)
print("Response: " + resp)
# Calling exec interactively
exec_command = ['/bin/sh']
resp = stream(api_instance.connect_get_namespaced_pod_exec,
name,
'default',
command=exec_command,
stderr=True, stdin=True,
stdout=True, tty=False,
_preload_content=False)
commands = [
"echo This message goes to stdout",
"echo \"This message goes to stderr\" >&2",
]
while resp.is_open():
resp.update(timeout=1)
if resp.peek_stdout():
print("STDOUT: %s" % resp.read_stdout())
if resp.peek_stderr():
print("STDERR: %s" % resp.read_stderr())
if commands:
c = commands.pop(0)
print("Running command... %s\n" % c)
resp.write_stdin(c + "\n")
else:
break
resp.write_stdin("date\n")
sdate = resp.readline_stdout(timeout=3)
print("Server date command returns: %s" % sdate)
resp.write_stdin("whoami\n")
user = resp.readline_stdout(timeout=3)
print("Server user is: %s" % user)
resp.close()
def main():
config.load_kube_config()
c = Configuration()
c.assert_hostname = False
Configuration.set_default(c)
core_v1 = core_v1_api.CoreV1Api()
exec_commands(core_v1)
if __name__ == '__main__':
main()

View File

@ -12,6 +12,13 @@
# See the License for the specific language governing permissions and
# limitations under the License.
"""
Uses watch to print the stream of events from list namespaces and list pods.
The script will wait for 10 events related to namespaces to occur within
the `timeout_seconds` threshold and then move on to wait for another 10 events
related to pods to occur within the `timeout_seconds` threshold.
"""
from kubernetes import client, config, watch
@ -29,8 +36,18 @@ def main():
count -= 1
if not count:
w.stop()
print("Finished namespace stream.")
print("Ended.")
for event in w.stream(v1.list_pod_for_all_namespaces, timeout_seconds=10):
print("Event: %s %s %s" % (
event['type'],
event['object'].kind,
event['object'].metadata.name)
)
count -= 1
if not count:
w.stop()
print("Finished pod stream.")
if __name__ == '__main__':

View File

@ -23,7 +23,7 @@ def main():
# Define the barer 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 = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
aToken = "<token>"
# Create a configuration object
aConfiguration = client.Configuration()

@ -1 +1 @@
Subproject commit 474e9fb32293fa05098e920967bb0e0645182d5b
Subproject commit 9f73cc68c1a93725f89842a7cd8c595204c1b901

View File

@ -17,12 +17,12 @@ import uuid
import yaml
from kubernetes.client import api_client
from kubernetes.client.apis import extensions_v1beta1_api
from kubernetes.client.apis import apps_v1_api
from kubernetes.client.models import v1_delete_options
from kubernetes.e2e_test import base
class TestClientExtensions(unittest.TestCase):
class TestClientApps(unittest.TestCase):
@classmethod
def setUpClass(cls):
@ -30,14 +30,17 @@ class TestClientExtensions(unittest.TestCase):
def test_create_deployment(self):
client = api_client.ApiClient(configuration=self.config)
api = extensions_v1beta1_api.ExtensionsV1beta1Api(client)
api = apps_v1_api.AppsV1Api(client)
name = 'nginx-deployment-' + str(uuid.uuid4())
deployment = '''apiVersion: extensions/v1beta1
deployment = '''apiVersion: apps/v1
kind: Deployment
metadata:
name: %s
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
@ -45,7 +48,7 @@ spec:
spec:
containers:
- name: nginx
image: nginx:1.7.9
image: nginx:1.15.4
ports:
- containerPort: 80
'''
@ -60,16 +63,19 @@ spec:
def test_create_daemonset(self):
client = api_client.ApiClient(configuration=self.config)
api = extensions_v1beta1_api.ExtensionsV1beta1Api(client)
api = apps_v1_api.AppsV1Api(client)
name = 'nginx-app-' + str(uuid.uuid4())
daemonset = {
'apiVersion': 'extensions/v1beta1',
'apiVersion': 'apps/v1',
'kind': 'DaemonSet',
'metadata': {
'labels': {'app': 'nginx'},
'name': '%s' % name,
},
'spec': {
'selector': {
'matchLabels': {'app': 'nginx'},
},
'template': {
'metadata': {
'labels': {'app': 'nginx'},
@ -77,7 +83,7 @@ spec:
'spec': {
'containers': [
{'name': 'nginx-app',
'image': 'nginx:1.10'},
'image': 'nginx:1.15.4'},
],
},
},
@ -91,4 +97,4 @@ spec:
self.assertIsNotNone(resp)
options = v1_delete_options.V1DeleteOptions()
resp = api.delete_namespaced_daemon_set(name, 'default', body=options)
resp = api.delete_namespaced_daemon_set(name, 'default', body=options)

View File

@ -29,6 +29,26 @@ def short_uuid():
return id[-12:]
def manifest_with_command(name, command):
return {
'apiVersion': 'v1',
'kind': 'Pod',
'metadata': {
'name': name
},
'spec': {
'containers': [{
'image': 'busybox',
'name': 'sleep',
"args": [
"/bin/sh",
"-c",
command
]
}]
}
}
class TestClient(unittest.TestCase):
@classmethod
@ -40,25 +60,7 @@ class TestClient(unittest.TestCase):
api = core_v1_api.CoreV1Api(client)
name = 'busybox-test-' + short_uuid()
pod_manifest = {
'apiVersion': 'v1',
'kind': 'Pod',
'metadata': {
'name': name
},
'spec': {
'containers': [{
'image': 'busybox',
'name': 'sleep',
"args": [
"/bin/sh",
"-c",
"while true;do date;sleep 5; done"
]
}]
}
}
pod_manifest = manifest_with_command(name, "while true;do date;sleep 5; done")
resp = api.create_namespaced_pod(body=pod_manifest,
namespace='default')
self.assertEqual(name, resp.metadata.name)
@ -117,6 +119,45 @@ class TestClient(unittest.TestCase):
resp = api.delete_namespaced_pod(name=name, body={},
namespace='default')
def test_exit_code(self):
client = api_client.ApiClient(configuration=self.config)
api = core_v1_api.CoreV1Api(client)
name = 'busybox-test-' + short_uuid()
pod_manifest = manifest_with_command(name, "while true;do date;sleep 5; done")
resp = api.create_namespaced_pod(body=pod_manifest,
namespace='default')
self.assertEqual(name, resp.metadata.name)
self.assertTrue(resp.status.phase)
while True:
resp = api.read_namespaced_pod(name=name,
namespace='default')
self.assertEqual(name, resp.metadata.name)
self.assertTrue(resp.status.phase)
if resp.status.phase == 'Running':
break
time.sleep(1)
commands_expected_values = (
(["false", 1]),
(["/bin/sh", "-c", "sleep 1; exit 3"], 3),
(["true", 0]),
(["/bin/sh", "-c", "ls /"], 0)
)
for command, value in commands_expected_values:
client = stream(api.connect_get_namespaced_pod_exec, name, 'default',
command=command,
stderr=True, stdin=False,
stdout=True, tty=False,
_preload_content=False)
self.assertIsNone(client.returncode)
client.run_forever(timeout=10)
self.assertEqual(client.returncode, value)
resp = api.delete_namespaced_pod(name=name, body={},
namespace='default')
def test_service_apis(self):
client = api_client.ApiClient(configuration=self.config)

View File

@ -14,7 +14,9 @@
import unittest
import yaml
from kubernetes import utils, client
from kubernetes.client.rest import ApiException
from kubernetes.e2e_test import base
@ -39,32 +41,39 @@ class TestUtils(unittest.TestCase):
def test_create_apps_deployment_from_yaml(self):
"""
Should be able to create an apps/v1beta1 deployment.
Should be able to create an apps/v1 deployment.
"""
k8s_client = client.api_client.ApiClient(configuration=self.config)
utils.create_from_yaml(
k8s_client, self.path_prefix + "apps-deployment.yaml")
app_api = client.AppsV1beta1Api(k8s_client)
app_api = client.AppsV1Api(k8s_client)
dep = app_api.read_namespaced_deployment(name="nginx-app",
namespace="default")
self.assertIsNotNone(dep)
app_api.delete_namespaced_deployment(
name="nginx-app", namespace="default",
body={})
while True:
try:
app_api.delete_namespaced_deployment(
name="nginx-app", namespace="default",
body={})
break
except ApiException:
continue
def test_create_extensions_deployment_from_yaml(self):
"""
Should be able to create an extensions/v1beta1 deployment.
"""
def test_create_apps_deployment_from_yaml_obj(self):
k8s_client = client.api_client.ApiClient(configuration=self.config)
utils.create_from_yaml(
k8s_client, self.path_prefix + "extensions-deployment.yaml")
ext_api = client.ExtensionsV1beta1Api(k8s_client)
dep = ext_api.read_namespaced_deployment(name="nginx-deployment",
with open(self.path_prefix + "apps-deployment.yaml") as f:
yml_obj = yaml.safe_load(f)
yml_obj["metadata"]["name"] = "nginx-app-3"
utils.create_from_dict(k8s_client, yml_obj)
app_api = client.AppsV1Api(k8s_client)
dep = app_api.read_namespaced_deployment(name="nginx-app-3",
namespace="default")
self.assertIsNotNone(dep)
ext_api.delete_namespaced_deployment(
name="nginx-deployment", namespace="default",
app_api.delete_namespaced_deployment(
name="nginx-app-3", namespace="default",
body={})
def test_create_pod_from_yaml(self):
@ -123,6 +132,20 @@ class TestUtils(unittest.TestCase):
rbac_api.delete_namespaced_role(
name="pod-reader", namespace="default", body={})
def test_create_rbac_role_from_yaml_with_verbose_enabled(self):
"""
Should be able to create an rbac role with verbose enabled.
"""
k8s_client = client.api_client.ApiClient(configuration=self.config)
utils.create_from_yaml(
k8s_client, self.path_prefix + "rbac-role.yaml", verbose=True)
rbac_api = client.RbacAuthorizationV1Api(k8s_client)
rbac_role = rbac_api.read_namespaced_role(
name="pod-reader", namespace="default")
self.assertIsNotNone(rbac_role)
rbac_api.delete_namespaced_role(
name="pod-reader", namespace="default", body={})
def test_create_deployment_non_default_namespace_from_yaml(self):
"""
Should be able to create a namespace "dep",
@ -134,7 +157,7 @@ class TestUtils(unittest.TestCase):
utils.create_from_yaml(
k8s_client, self.path_prefix + "dep-deployment.yaml")
core_api = client.CoreV1Api(k8s_client)
ext_api = client.ExtensionsV1beta1Api(k8s_client)
ext_api = client.AppsV1Api(k8s_client)
nmsp = core_api.read_namespace(name="dep")
self.assertIsNotNone(nmsp)
dep = ext_api.read_namespaced_deployment(name="nginx-deployment",
@ -186,7 +209,7 @@ class TestUtils(unittest.TestCase):
utils.create_from_yaml(
k8s_client, self.path_prefix + "list.yaml")
core_api = client.CoreV1Api(k8s_client)
ext_api = client.ExtensionsV1beta1Api(k8s_client)
ext_api = client.AppsV1Api(k8s_client)
svc = core_api.read_namespaced_service(name="list-service-test",
namespace="default")
self.assertIsNotNone(svc)
@ -266,7 +289,7 @@ class TestUtils(unittest.TestCase):
utils.create_from_yaml(
k8s_client, self.path_prefix + "multi-resource-with-list.yaml")
core_api = client.CoreV1Api(k8s_client)
app_api = client.AppsV1beta1Api(k8s_client)
app_api = client.AppsV1Api(k8s_client)
pod_0 = core_api.read_namespaced_pod(
name="mock-pod-0", namespace="default")
self.assertIsNotNone(pod_0)
@ -317,7 +340,7 @@ class TestUtils(unittest.TestCase):
def test_create_from_multi_resource_yaml_with_multi_conflicts(self):
"""
Should create an extensions/v1beta1 deployment
Should create an apps/v1 deployment
and fail to create the same deployment twice.
Should raise an exception that contains two error messages.
"""
@ -327,14 +350,14 @@ class TestUtils(unittest.TestCase):
k8s_client, self.path_prefix + "triple-nginx.yaml")
exp_error = ('Error from server (Conflict): {"kind":"Status",'
'"apiVersion":"v1","metadata":{},"status":"Failure",'
'"message":"deployments.extensions \\"triple-nginx\\" '
'"message":"deployments.apps \\"triple-nginx\\" '
'already exists","reason":"AlreadyExists",'
'"details":{"name":"triple-nginx","group":"extensions",'
'"details":{"name":"triple-nginx","group":"apps",'
'"kind":"deployments"},"code":409}\n'
)
exp_error += exp_error
self.assertEqual(exp_error, str(cm.exception))
ext_api = client.ExtensionsV1beta1Api(k8s_client)
ext_api = client.AppsV1Api(k8s_client)
dep = ext_api.read_namespaced_deployment(name="triple-nginx",
namespace="default")
self.assertIsNotNone(dep)
@ -342,14 +365,16 @@ class TestUtils(unittest.TestCase):
name="triple-nginx", namespace="default",
body={})
def test_create_namespaces_apps_deployment_from_yaml(self):
def test_create_namespaced_apps_deployment_from_yaml(self):
"""
Should be able to create an apps/v1beta1 deployment.
Should be able to create an apps/v1beta1 deployment
in a test namespace.
"""
k8s_client = client.api_client.ApiClient(configuration=self.config)
utils.create_from_yaml(
k8s_client, self.path_prefix + "apps-deployment.yaml", namespace=self.test_namespace)
app_api = client.AppsV1beta1Api(k8s_client)
k8s_client, self.path_prefix + "apps-deployment.yaml",
namespace=self.test_namespace)
app_api = client.AppsV1Api(k8s_client)
dep = app_api.read_namespaced_deployment(name="nginx-app",
namespace=self.test_namespace)
self.assertIsNotNone(dep)
@ -357,16 +382,17 @@ class TestUtils(unittest.TestCase):
name="nginx-app", namespace=self.test_namespace,
body={})
def test_create_from_list_in_multi_resource_yaml(self):
def test_create_from_list_in_multi_resource_yaml_namespaced(self):
"""
Should be able to create the items in the PodList and a deployment
specified in the multi-resource file
specified in the multi-resource file in a test namespace
"""
k8s_client = client.api_client.ApiClient(configuration=self.config)
utils.create_from_yaml(
k8s_client, self.path_prefix + "multi-resource-with-list.yaml", namespace=self.test_namespace)
k8s_client, self.path_prefix + "multi-resource-with-list.yaml",
namespace=self.test_namespace)
core_api = client.CoreV1Api(k8s_client)
app_api = client.AppsV1beta1Api(k8s_client)
app_api = client.AppsV1Api(k8s_client)
pod_0 = core_api.read_namespaced_pod(
name="mock-pod-0", namespace=self.test_namespace)
self.assertIsNotNone(pod_0)

View File

@ -1,9 +1,14 @@
apiVersion: extensions/v1beta1
apiVersion: apps/v1beta1
kind: Deployment
metadata:
name: nginx-deployment
name: nginx-app-2
labels:
app: nginx
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
@ -11,7 +16,6 @@ spec:
spec:
containers:
- name: nginx
image: nginx:1.7.9
image: nginx:1.15.4
ports:
- containerPort: 80

View File

@ -1,4 +1,4 @@
apiVersion: apps/v1beta1
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-app

View File

@ -1,10 +1,13 @@
apiVersion: extensions/v1beta1
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
namespace: dep
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
@ -12,7 +15,6 @@ spec:
spec:
containers:
- name: nginx
image: nginx:1.7.9
image: nginx:1.15.4
ports:
- containerPort: 80

View File

@ -11,7 +11,7 @@ items:
port: 80
selector:
app: list-deployment-test
- apiVersion: extensions/v1beta1
- apiVersion: apps/v1
kind: Deployment
metadata:
name: list-deployment-test
@ -19,6 +19,9 @@ items:
app: list-deployment-test
spec:
replicas: 1
selector:
matchLabels:
app: list-deployment-test
template:
metadata:
labels:
@ -26,4 +29,4 @@ items:
spec:
containers:
- name: nginx
image: nginx
image: nginx:1.15.4

View File

@ -24,7 +24,7 @@ items:
image: busybox
command: ['sh', '-c', 'echo Hello Kubernetes! && sleep 3600']
---
apiVersion: apps/v1beta1
apiVersion: apps/v1
kind: Deployment
metadata:
name: mock

View File

@ -1,9 +1,12 @@
apiVersion: extensions/v1beta1
apiVersion: apps/v1
kind: Deployment
metadata:
name: triple-nginx
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
@ -11,16 +14,19 @@ spec:
spec:
containers:
- name: nginx
image: nginx:1.7.9
image: nginx:1.15.4
ports:
- containerPort: 80
---
apiVersion: extensions/v1beta1
apiVersion: apps/v1
kind: Deployment
metadata:
name: triple-nginx
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
@ -28,16 +34,19 @@ spec:
spec:
containers:
- name: nginx
image: nginx:1.7.9
image: nginx:1.15.4
ports:
- containerPort: 80
---
apiVersion: extensions/v1beta1
apiVersion: apps/v1
kind: Deployment
metadata:
name: triple-nginx
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
@ -45,6 +54,6 @@ spec:
spec:
containers:
- name: nginx
image: nginx:1.7.9
image: nginx:1.15.4
ports:
- containerPort: 80

View File

@ -0,0 +1,112 @@
# coding: utf-8
# Copyright 2019 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.
from __future__ import absolute_import
import unittest
from kubernetes.utils import parse_quantity
from decimal import Decimal
class TestQuantity(unittest.TestCase):
def test_parse(self):
self.assertIsInstance(parse_quantity(2.2), Decimal)
# input, expected output
tests = [
(0, 0),
(2, 2),
(2, Decimal("2")),
(2., 2),
(Decimal("2.2"), Decimal("2.2")),
(2., Decimal(2)),
(Decimal("2."), 2),
("123", 123),
("2", 2),
("2n", Decimal("2") * Decimal(1000)**-3),
("2u", Decimal("0.000002")),
("2m", Decimal("0.002")),
("0m", Decimal("0")),
("0M", Decimal("0")),
("223k", 223000),
("002M", 2 * 1000**2),
("2M", 2 * 1000**2),
("4123G", 4123 * 1000**3),
("2T", 2 * 1000**4),
("2P", 2 * 1000**5),
("2E", 2 * 1000**6),
("223Ki", 223 * 1024),
("002Mi", 2 * 1024**2),
("2Mi", 2 * 1024**2),
("2Gi", 2 * 1024**3),
("4123Gi", 4123 * 1024**3),
("2Ti", 2 * 1024**4),
("2Pi", 2 * 1024**5),
("2Ei", 2 * 1024**6),
("2.34n", Decimal("2.34") * Decimal(1000)**-3),
("2.34u", Decimal("2.34") * Decimal(1000)**-2),
("2.34m", Decimal("2.34") * Decimal(1000)**-1),
("2.34Ki", Decimal("2.34") * 1024),
("2.34", Decimal("2.34")),
(".34", Decimal("0.34")),
("34.", 34),
(".34M", Decimal("0.34") * 1000**2),
("2e2K", Decimal("2e2") * 1000),
("2e2Ki", Decimal("2e2") * 1024),
("2e-2Ki", Decimal("2e-2") * 1024),
("2.34E1", Decimal("2.34E1")),
(".34e-2", Decimal("0.34e-2")),
]
for inp, out in tests:
self.assertEqual(parse_quantity(inp), out)
if isinstance(inp, (int, float, Decimal)):
self.assertEqual(parse_quantity(-1 * inp), -out)
else:
self.assertEqual(parse_quantity("-" + inp), -out)
self.assertEqual(parse_quantity("+" + inp), out)
def test_parse_invalid(self):
self.assertRaises(ValueError, parse_quantity, [])
self.assertRaises(ValueError, parse_quantity, "")
self.assertRaises(ValueError, parse_quantity, "-")
self.assertRaises(ValueError, parse_quantity, "i")
self.assertRaises(ValueError, parse_quantity, "2i")
self.assertRaises(ValueError, parse_quantity, "2mm")
self.assertRaises(ValueError, parse_quantity, "2mmKi")
self.assertRaises(ValueError, parse_quantity, "2KKi")
self.assertRaises(ValueError, parse_quantity, "2e")
self.assertRaises(ValueError, parse_quantity, "2.2i")
self.assertRaises(ValueError, parse_quantity, "bla")
self.assertRaises(ValueError, parse_quantity, "Ki")
self.assertRaises(ValueError, parse_quantity, "M")
self.assertRaises(ValueError, parse_quantity, "2ki")
self.assertRaises(ValueError, parse_quantity, "2Ki ")
self.assertRaises(ValueError, parse_quantity, "20Ki ")
self.assertRaises(ValueError, parse_quantity, "20B")
self.assertRaises(ValueError, parse_quantity, "20Bi")
self.assertRaises(ValueError, parse_quantity, "20.2Bi")
self.assertRaises(ValueError, parse_quantity, "2MiKi")
self.assertRaises(ValueError, parse_quantity, "2MK")
self.assertRaises(ValueError, parse_quantity, "2MKi")
self.assertRaises(ValueError, parse_quantity, "234df")
self.assertRaises(ValueError, parse_quantity, "df234")
self.assertRaises(ValueError, parse_quantity, tuple())
if __name__ == '__main__':
unittest.main()

View File

@ -14,4 +14,6 @@
from __future__ import absolute_import
from .create_from_yaml import FailToCreateError, create_from_yaml
from .create_from_yaml import (FailToCreateError, create_from_dict,
create_from_yaml)
from .quantity import parse_quantity

View File

@ -41,14 +41,6 @@ def create_from_yaml(
the yaml file already contains a namespace definition
this parameter has no effect.
Returns:
An k8s api object or list of apis objects created from YAML.
When a single object is generated, return type is dependent
on output_list.
Throws a FailToCreateError exception if creation of any object
fails with helpful messages from the server.
Available parameters for creating <kind>:
:param async_req bool
:param bool include_uninitialized: If true, partially initialized
@ -59,47 +51,81 @@ def create_from_yaml(
directive will result in an error response and no further
processing of the request.
Valid values are: - All: all dry run stages will be processed
"""
Raises:
FailToCreateError which holds list of `client.rest.ApiException`
instances for each object that failed to create.
"""
with open(path.abspath(yaml_file)) as f:
yml_document_all = yaml.safe_load_all(f)
api_exceptions = []
# Load all documents from a single YAML file
failures = []
for yml_document in yml_document_all:
# If it is a list type, will need to iterate its items
if "List" in yml_document["kind"]:
# Could be "List" or "Pod/Service/...List"
# This is a list type. iterate within its items
kind = yml_document["kind"].replace("List", "")
for yml_object in yml_document["items"]:
# Mitigate cases when server returns a xxxList object
# See kubernetes-client/python#586
if kind is not "":
yml_object["apiVersion"] = yml_document["apiVersion"]
yml_object["kind"] = kind
try:
create_from_yaml_single_item(
k8s_client, yml_object, verbose, namespace, **kwargs)
except client.rest.ApiException as api_exception:
api_exceptions.append(api_exception)
else:
# This is a single object. Call the single item method
try:
create_from_yaml_single_item(
k8s_client, yml_document, verbose, namespace, **kwargs)
except client.rest.ApiException as api_exception:
api_exceptions.append(api_exception)
try:
create_from_dict(k8s_client, yml_document, verbose,
namespace=namespace,
**kwargs)
except FailToCreateError as failure:
failures.extend(failure.api_exceptions)
if failures:
raise FailToCreateError(failures)
def create_from_dict(k8s_client, data, verbose=False, namespace='default',
**kwargs):
"""
Perform an action from a dictionary containing valid kubernetes
API object (i.e. List, Service, etc).
Input:
k8s_client: an ApiClient object, initialized with the client args.
data: a dictionary holding valid kubernetes objects
verbose: If True, print confirmation from the create action.
Default is False.
namespace: string. Contains the namespace to create all
resources inside. The namespace must preexist otherwise
the resource creation will fail. If the API object in
the yaml file already contains a namespace definition
this parameter has no effect.
Raises:
FailToCreateError which holds list of `client.rest.ApiException`
instances for each object that failed to create.
"""
# If it is a list type, will need to iterate its items
api_exceptions = []
if "List" in data["kind"]:
# Could be "List" or "Pod/Service/...List"
# This is a list type. iterate within its items
kind = data["kind"].replace("List", "")
for yml_object in data["items"]:
# Mitigate cases when server returns a xxxList object
# See kubernetes-client/python#586
if kind is not "":
yml_object["apiVersion"] = data["apiVersion"]
yml_object["kind"] = kind
try:
create_from_yaml_single_item(
k8s_client, yml_object, verbose, namespace=namespace,
**kwargs)
except client.rest.ApiException as api_exception:
api_exceptions.append(api_exception)
else:
# This is a single object. Call the single item method
try:
create_from_yaml_single_item(
k8s_client, data, verbose, namespace=namespace, **kwargs)
except client.rest.ApiException as api_exception:
api_exceptions.append(api_exception)
# In case we have exceptions waiting for us, raise them
if api_exceptions:
raise FailToCreateError(api_exceptions)
def create_from_yaml_single_item(
k8s_client,
yml_object,
verbose=False,
namespace="default",
**kwargs):
k8s_client, yml_object, verbose=False, **kwargs):
group, _, version = yml_object["apiVersion"].partition("/")
if version == "":
version = group
@ -116,19 +142,24 @@ def create_from_yaml_single_item(
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()
# Decide which namespace we are going to put the object in,
# if any
if "namespace" in yml_object["metadata"]:
namespace = yml_object["metadata"]["namespace"]
# 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,
# if any
if "namespace" in yml_object["metadata"]:
namespace = yml_object["metadata"]["namespace"]
kwargs['namespace'] = namespace
resp = getattr(k8s_api, "create_namespaced_{0}".format(kind))(
body=yml_object, namespace=namespace, **kwargs)
body=yml_object, **kwargs)
else:
kwargs.pop('namespace', None)
resp = getattr(k8s_api, "create_{0}".format(kind))(
body=yml_object, **kwargs)
if verbose:
print("{0} created. status='{1}'".format(kind, str(resp.status)))
msg = "{0} created.".format(kind)
if hasattr(resp, 'status'):
msg += " status='{0}'".format(str(resp.status))
print(msg)
class FailToCreateError(Exception):

View File

@ -0,0 +1,75 @@
# Copyright 2019 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.
from decimal import Decimal, InvalidOperation
def parse_quantity(quantity):
"""
Parse kubernetes canonical form quantity like 200Mi to a decimal number.
Supported SI suffixes:
base1024: Ki | Mi | Gi | Ti | Pi | Ei
base1000: n | u | m | "" | k | M | G | T | P | E
See https://github.com/kubernetes/apimachinery/blob/master/pkg/api/resource/quantity.go
Input:
quanity: string. kubernetes canonical form quantity
Returns:
Decimal
Raises:
ValueError on invalid or unknown input
"""
if isinstance(quantity, (int, float, Decimal)):
return Decimal(quantity)
exponents = {"n": -3, "u": -2, "m": -1, "K": 1, "k": 1, "M": 2,
"G": 3, "T": 4, "P": 5, "E": 6}
quantity = str(quantity)
number = quantity
suffix = None
if len(quantity) >= 2 and quantity[-1] == "i":
if quantity[-2] in exponents:
number = quantity[:-2]
suffix = quantity[-2:]
elif len(quantity) >= 1 and quantity[-1] in exponents:
number = quantity[:-1]
suffix = quantity[-1:]
try:
number = Decimal(number)
except InvalidOperation:
raise ValueError("Invalid number format: {}".format(number))
if suffix is None:
return number
if suffix.endswith("i"):
base = 1024
elif len(suffix) == 1:
base = 1000
else:
raise ValueError("{} has unknown suffix".format(quantity))
# handly SI inconsistency
if suffix == "ki":
raise ValueError("{} has unknown suffix".format(quantity))
if suffix[0] not in exponents:
raise ValueError("{} has unknown suffix".format(quantity))
exponent = Decimal(exponents[suffix[0]])
return number * (base ** exponent)

View File

@ -8,4 +8,4 @@ ipaddress>=1.0.17;python_version=="2.7" # PSF
websocket-client>=0.32.0,!=0.40.0,!=0.41.*,!=0.42.* # LGPLv2+
requests # Apache-2.0
requests-oauthlib # ISC
urllib3>=1.23 # MIT
urllib3>=1.24.2 # MIT

View File

@ -108,7 +108,8 @@ done
# Shut down CI if minikube did not start and show logs
if [ $MINIKUBE_OK == "false" ]; then
sudo minikube logs
die $LINENO "minikube did not start"
echo "minikube did not start (line: ${LINENO})"
exit 1
fi
echo "Dump Kubernetes Objects..."

View File

@ -21,11 +21,6 @@ set -o errexit
set -o nounset
set -o pipefail
if ! which mvn > /dev/null 2>&1; then
echo "Maven is not installed."
exit
fi
SCRIPT_ROOT=$(dirname "${BASH_SOURCE}")
CLIENT_ROOT="${SCRIPT_ROOT}/../kubernetes"
CLIENT_VERSION=$(python "${SCRIPT_ROOT}/constants.py" CLIENT_VERSION)

View File

@ -62,9 +62,7 @@ setup(
'kubernetes.stream', 'kubernetes.client.models',
'kubernetes.utils'],
include_package_data=True,
long_description="""\
Python client for kubernetes http://kubernetes.io/
""",
long_description="Python client for kubernetes http://kubernetes.io/",
classifiers=[
"Development Status :: %s" % DEVELOPMENT_STATUS,
"Topic :: Utilities",
@ -76,9 +74,9 @@ setup(
"Programming Language :: Python :: 2",
"Programming Language :: Python :: 2.7",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.4",
"Programming Language :: Python :: 3.5",
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
],
)

View File

@ -1,13 +1,15 @@
coverage>=4.0.3
nose>=1.3.7
pytest
pytest-cov
pluggy>=0.3.1
py>=1.4.31
randomize>=0.13
mock>=2.0.0
sphinx>=1.2.1,!=1.3b1,<1.4 # BSD
sphinx>=1.4 # BSD
recommonmark
sphinx_markdown_tables
codecov>=1.4.0
pycodestyle
autopep8
isort
isort

29
tox.ini
View File

@ -1,5 +1,7 @@
[tox]
envlist = py27, py34, py35, py36, py37
envlist =
py27, py3{5,6,7,8}
py27-functional, py3{5,6,7,8}-functional
[testenv]
passenv = TOXENV CI TRAVIS TRAVIS_*
@ -9,7 +11,8 @@ deps = -r{toxinidir}/test-requirements.txt
-r{toxinidir}/requirements.txt
commands =
python -V
py.test -vvv -s --ignore=kubernetes/e2e_test
!functional: pytest -vvv -s --ignore=kubernetes/e2e_test
functional: {toxinidir}/scripts/kube-init.sh pytest -vvv -s []
[testenv:docs]
commands =
@ -19,30 +22,10 @@ commands =
commands =
{toxinidir}/scripts/update-pycodestyle.sh
[testenv:py27-functional]
commands =
python -V
{toxinidir}/scripts/kube-init.sh py.test -vvv -s []
[testenv:py35-functional]
commands =
python -V
{toxinidir}/scripts/kube-init.sh py.test -vvv -s []
[testenv:py36-functional]
commands =
python -V
{toxinidir}/scripts/kube-init.sh py.test -vvv -s []
[testenv:py37-functional]
commands =
python -V
{toxinidir}/scripts/kube-init.sh py.test -vvv -s []
[testenv:coverage]
commands =
python -V
nosetests --with-coverage --cover-package=kubernetes.config,kubernetes.watch --cover-tests
pytest --cov=kubernetes/watch --cov=kubernetes/config kubernetes/watch kubernetes/config
[testenv:codecov]
commands =