|
| 1 | +# Copyright 2018 The Kubernetes Authors. |
| 2 | +# |
| 3 | +# Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | +# you may not use this file except in compliance with the License. |
| 5 | +# You may obtain a copy of the License at |
| 6 | +# |
| 7 | +# http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | +# |
| 9 | +# Unless required by applicable law or agreed to in writing, software |
| 10 | +# distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | +# See the License for the specific language governing permissions and |
| 13 | +# limitations under the License. |
| 14 | + |
| 15 | + |
| 16 | +import re |
| 17 | +from os import path |
| 18 | + |
| 19 | +import yaml |
| 20 | + |
| 21 | +from kubernetes_asyncio import client |
| 22 | + |
| 23 | + |
| 24 | +async def create_from_yaml( |
| 25 | + k8s_client, |
| 26 | + yaml_file, |
| 27 | + verbose=False, |
| 28 | + namespace="default", |
| 29 | + **kwargs): |
| 30 | + """ |
| 31 | + Perform an action from a yaml file. Pass True for verbose to |
| 32 | + print confirmation information. |
| 33 | + Input: |
| 34 | + yaml_file: string. Contains the path to yaml file. |
| 35 | + k8s_client: an ApiClient object, initialized with the client args. |
| 36 | + verbose: If True, print confirmation from the create action. |
| 37 | + Default is False. |
| 38 | + namespace: string. Contains the namespace to create all |
| 39 | + resources inside. The namespace must preexist otherwise |
| 40 | + the resource creation will fail. If the API object in |
| 41 | + the yaml file already contains a namespace definition |
| 42 | + this parameter has no effect. |
| 43 | + Returns: |
| 44 | + An k8s api object or list of apis objects created from YAML. |
| 45 | + When a single object is generated, return type is dependent |
| 46 | + on output_list. |
| 47 | + Throws a FailToCreateError exception if creation of any object |
| 48 | + fails with helpful messages from the server. |
| 49 | + Available parameters for creating <kind>: |
| 50 | + :param async_req bool |
| 51 | + :param bool include_uninitialized: If true, partially initialized |
| 52 | + resources are included in the response. |
| 53 | + :param str pretty: If 'true', then the output is pretty printed. |
| 54 | + :param str dry_run: When present, indicates that modifications |
| 55 | + should not be persisted. An invalid or unrecognized dryRun |
| 56 | + directive will result in an error response and no further |
| 57 | + processing of the request. |
| 58 | + Valid values are: - All: all dry run stages will be processed |
| 59 | + """ |
| 60 | + |
| 61 | + with open(path.abspath(yaml_file)) as f: |
| 62 | + yml_document_all = yaml.safe_load_all(f) |
| 63 | + api_exceptions = [] |
| 64 | + # Load all documents from a single YAML file |
| 65 | + for yml_document in yml_document_all: |
| 66 | + # If it is a list type, will need to iterate its items |
| 67 | + if "List" in yml_document["kind"]: |
| 68 | + # Could be "List" or "Pod/Service/...List" |
| 69 | + # This is a list type. iterate within its items |
| 70 | + kind = yml_document["kind"].replace("List", "") |
| 71 | + for yml_object in yml_document["items"]: |
| 72 | + # Mitigate cases when server returns a xxxList object |
| 73 | + # See kubernetes-client/python#586 |
| 74 | + if kind is not "": |
| 75 | + yml_object["apiVersion"] = yml_document["apiVersion"] |
| 76 | + yml_object["kind"] = kind |
| 77 | + try: |
| 78 | + await create_from_yaml_single_item( |
| 79 | + k8s_client, yml_object, verbose, namespace, **kwargs) |
| 80 | + except client.rest.ApiException as api_exception: |
| 81 | + api_exceptions.append(api_exception) |
| 82 | + else: |
| 83 | + # This is a single object. Call the single item method |
| 84 | + try: |
| 85 | + await create_from_yaml_single_item( |
| 86 | + k8s_client, yml_document, verbose, namespace, **kwargs) |
| 87 | + except client.rest.ApiException as api_exception: |
| 88 | + api_exceptions.append(api_exception) |
| 89 | + # In case we have exceptions waiting for us, raise them |
| 90 | + if api_exceptions: |
| 91 | + raise FailToCreateError(api_exceptions) |
| 92 | + |
| 93 | + |
| 94 | +async def create_from_yaml_single_item( |
| 95 | + k8s_client, |
| 96 | + yml_object, |
| 97 | + verbose=False, |
| 98 | + namespace="default", |
| 99 | + **kwargs): |
| 100 | + group, _, version = yml_object["apiVersion"].partition("/") |
| 101 | + if version == "": |
| 102 | + version = group |
| 103 | + group = "core" |
| 104 | + # Take care for the case e.g. api_type is "apiextensions.k8s.io" |
| 105 | + # Only replace the last instance |
| 106 | + group = "".join(group.rsplit(".k8s.io", 1)) |
| 107 | + # convert group name from DNS subdomain format to |
| 108 | + # python class name convention |
| 109 | + group = "".join(word.capitalize() for word in group.split('.')) |
| 110 | + fcn_to_call = "{0}{1}Api".format(group, version.capitalize()) |
| 111 | + k8s_api = getattr(client, fcn_to_call)(k8s_client) |
| 112 | + # Replace CamelCased action_type into snake_case |
| 113 | + kind = yml_object["kind"] |
| 114 | + kind = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', kind) |
| 115 | + kind = re.sub('([a-z0-9])([A-Z])', r'\1_\2', kind).lower() |
| 116 | + # Decide which namespace we are going to put the object in, |
| 117 | + # if any |
| 118 | + if "namespace" in yml_object["metadata"]: |
| 119 | + namespace = yml_object["metadata"]["namespace"] |
| 120 | + # Expect the user to create namespaced objects more often |
| 121 | + if hasattr(k8s_api, "create_namespaced_{0}".format(kind)): |
| 122 | + resp = await getattr(k8s_api, "create_namespaced_{0}".format(kind))( |
| 123 | + body=yml_object, namespace=namespace, **kwargs) |
| 124 | + else: |
| 125 | + resp = await getattr(k8s_api, "create_{0}".format(kind))( |
| 126 | + body=yml_object, **kwargs) |
| 127 | + if verbose: |
| 128 | + print("{0} created. status='{1}'".format(kind, str(resp.status))) |
| 129 | + |
| 130 | + |
| 131 | +class FailToCreateError(Exception): |
| 132 | + """ |
| 133 | + An exception class for handling error if an error occurred when |
| 134 | + handling a yaml file. |
| 135 | + """ |
| 136 | + |
| 137 | + def __init__(self, api_exceptions): |
| 138 | + self.api_exceptions = api_exceptions |
| 139 | + |
| 140 | + def __str__(self): |
| 141 | + msg = "" |
| 142 | + for api_exception in self.api_exceptions: |
| 143 | + msg += "Error from server ({0}): {1}".format( |
| 144 | + api_exception.reason, api_exception.body) |
| 145 | + return msg |
0 commit comments