Skip to content

Commit

Permalink
Merge pull request kubernetes-client#795 from oz123/util-improvements
Browse files Browse the repository at this point in the history
Allow creation from dict
  • Loading branch information
k8s-ci-robot authored Jul 23, 2019
2 parents 693f115 + e867c29 commit b8cb6e8
Show file tree
Hide file tree
Showing 4 changed files with 120 additions and 46 deletions.
26 changes: 25 additions & 1 deletion kubernetes/e2e_test/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand Down Expand Up @@ -48,8 +50,30 @@ def test_create_apps_deployment_from_yaml(self):
dep = app_api.read_namespaced_deployment(name="nginx-app",
namespace="default")
self.assertIsNotNone(dep)
while True:
try:
app_api.delete_namespaced_deployment(
name="nginx-app", namespace="default",
body={})
break
except ApiException:
continue

def test_create_apps_deployment_from_yaml_obj(self):
k8s_client = client.api_client.ApiClient(configuration=self.config)
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.AppsV1beta1Api(k8s_client)
dep = app_api.read_namespaced_deployment(name="nginx-app-3",
namespace="default")
self.assertIsNotNone(dep)
app_api.delete_namespaced_deployment(
name="nginx-app", namespace="default",
name="nginx-app-3", namespace="default",
body={})

def test_create_extensions_deployment_from_yaml(self):
Expand Down
21 changes: 21 additions & 0 deletions kubernetes/e2e_test/test_yaml/apps-deployment-2.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
apiVersion: apps/v1beta1
kind: Deployment
metadata:
name: nginx-app-2
labels:
app: nginx
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.15.4
ports:
- containerPort: 80
3 changes: 2 additions & 1 deletion kubernetes/utils/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,5 @@

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)
116 changes: 72 additions & 44 deletions kubernetes/utils/create_from_yaml.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -116,15 +142,17 @@ 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:
Expand Down

0 comments on commit b8cb6e8

Please sign in to comment.