1- # Copyright 2018 The Kubernetes Authors.
1+ # Copyright 2019 The Kubernetes Authors.
22#
33# Licensed under the Apache License, Version 2.0 (the "License");
44# you may not use this file except in compliance with the License.
1313# limitations under the License.
1414
1515
16- import re
1716import os
17+ import re
1818
1919import yaml
20-
2120from kubernetes import client
21+ from kubernetes .dynamic .client import DynamicClient
2222
23- UPPER_FOLLOWED_BY_LOWER_RE = re .compile (' (.)([A-Z][a-z]+)' )
24- LOWER_OR_NUM_FOLLOWED_BY_UPPER_RE = re .compile (' ([a-z0-9])([A-Z])' )
23+ UPPER_FOLLOWED_BY_LOWER_RE = re .compile (" (.)([A-Z][a-z]+)" )
24+ LOWER_OR_NUM_FOLLOWED_BY_UPPER_RE = re .compile (" ([a-z0-9])([A-Z])" )
2525
2626
2727def create_from_directory (
28- k8s_client ,
29- yaml_dir = None ,
30- verbose = False ,
31- namespace = "default" ,
32- ** kwargs ):
28+ k8s_client , yaml_dir = None , verbose = False , namespace = "default" , apply = False , ** kwargs
29+ ):
3330 """
3431 Perform an action from files from a directory. Pass True for verbose to
3532 print confirmation information.
@@ -44,6 +41,7 @@ def create_from_directory(
4441 the resource creation will fail. If the API object in
4542 the yaml file already contains a namespace definition
4643 this parameter has no effect.
44+ apply: bool. If True, use server-side apply for creating resources.
4745
4846 Available parameters for creating <kind>:
4947 :param async_req bool
@@ -65,27 +63,31 @@ def create_from_directory(
6563 """
6664
6765 if not yaml_dir :
68- raise ValueError (
69- '`yaml_dir` argument must be provided' )
66+ raise ValueError ("`yaml_dir` argument must be provided" )
7067 elif not os .path .isdir (yaml_dir ):
71- raise ValueError (
72- '`yaml_dir` argument must be a path to directory' )
68+ raise ValueError ("`yaml_dir` argument must be a path to directory" )
7369
74- files = [os .path .join (yaml_dir , i ) for i in os .listdir (yaml_dir )
75- if os .path .isfile (os .path .join (yaml_dir , i ))]
70+ files = [
71+ os .path .join (yaml_dir , i )
72+ for i in os .listdir (yaml_dir )
73+ if os .path .isfile (os .path .join (yaml_dir , i ))
74+ ]
7675 if not files :
77- raise ValueError (
78- '`yaml_dir` contains no files' )
76+ raise ValueError ("`yaml_dir` contains no files" )
7977
8078 failures = []
8179 k8s_objects_all = []
8280
8381 for file in files :
8482 try :
85- k8s_objects = create_from_yaml (k8s_client , file ,
86- verbose = verbose ,
87- namespace = namespace ,
88- ** kwargs )
83+ k8s_objects = create_from_yaml (
84+ k8s_client ,
85+ file ,
86+ verbose = verbose ,
87+ namespace = namespace ,
88+ apply = apply ,
89+ ** kwargs ,
90+ )
8991 k8s_objects_all .append (k8s_objects )
9092 except FailToCreateError as failure :
9193 failures .extend (failure .api_exceptions )
@@ -95,12 +97,14 @@ def create_from_directory(
9597
9698
9799def create_from_yaml (
98- k8s_client ,
99- yaml_file = None ,
100- yaml_objects = None ,
101- verbose = False ,
102- namespace = "default" ,
103- ** kwargs ):
100+ k8s_client ,
101+ yaml_file = None ,
102+ yaml_objects = None ,
103+ verbose = False ,
104+ namespace = "default" ,
105+ apply = False ,
106+ ** kwargs ,
107+ ):
104108 """
105109 Perform an action from a yaml file. Pass True for verbose to
106110 print confirmation information.
@@ -116,6 +120,7 @@ def create_from_yaml(
116120 the resource creation will fail. If the API object in
117121 the yaml file already contains a namespace definition
118122 this parameter has no effect.
123+ apply: bool. If True, use server-side apply for creating resources.
119124
120125 Available parameters for creating <kind>:
121126 :param async_req bool
@@ -136,16 +141,21 @@ def create_from_yaml(
136141 instances for each object that failed to create.
137142 """
138143
139- def create_with (objects ):
144+ def create_with (objects , apply = apply ):
140145 failures = []
141146 k8s_objects = []
142147 for yml_document in objects :
143148 if yml_document is None :
144149 continue
145150 try :
146- created = create_from_dict (k8s_client , yml_document , verbose ,
147- namespace = namespace ,
148- ** kwargs )
151+ created = create_from_dict (
152+ k8s_client ,
153+ yml_document ,
154+ verbose ,
155+ namespace = namespace ,
156+ apply = apply ,
157+ ** kwargs ,
158+ )
149159 k8s_objects .append (created )
150160 except FailToCreateError as failure :
151161 failures .extend (failure .api_exceptions )
@@ -164,14 +174,16 @@ class Loader(yaml.loader.SafeLoader):
164174 elif yaml_file :
165175 with open (os .path .abspath (yaml_file )) as f :
166176 yml_document_all = yaml .load_all (f , Loader = Loader )
167- return create_with (yml_document_all )
177+ return create_with (yml_document_all , apply )
168178 else :
169179 raise ValueError (
170- 'One of `yaml_file` or `yaml_objects` arguments must be provided' )
180+ "One of `yaml_file` or `yaml_objects` arguments must be provided"
181+ )
171182
172183
173- def create_from_dict (k8s_client , data , verbose = False , namespace = 'default' ,
174- ** kwargs ):
184+ def create_from_dict (
185+ k8s_client , data , verbose = False , namespace = "default" , apply = False , ** kwargs
186+ ):
175187 """
176188 Perform an action from a dictionary containing valid kubernetes
177189 API object (i.e. List, Service, etc).
@@ -186,6 +198,7 @@ def create_from_dict(k8s_client, data, verbose=False, namespace='default',
186198 the resource creation will fail. If the API object in
187199 the yaml file already contains a namespace definition
188200 this parameter has no effect.
201+ apply: bool. If True, use server-side apply for creating resources.
189202
190203 Returns:
191204 The created kubernetes API objects.
@@ -210,16 +223,22 @@ def create_from_dict(k8s_client, data, verbose=False, namespace='default',
210223 yml_object ["kind" ] = kind
211224 try :
212225 created = create_from_yaml_single_item (
213- k8s_client , yml_object , verbose , namespace = namespace ,
214- ** kwargs )
226+ k8s_client ,
227+ yml_object ,
228+ verbose ,
229+ namespace = namespace ,
230+ apply = apply ,
231+ ** kwargs ,
232+ )
215233 k8s_objects .append (created )
216234 except client .rest .ApiException as api_exception :
217235 api_exceptions .append (api_exception )
218236 else :
219237 # This is a single object. Call the single item method
220238 try :
221239 created = create_from_yaml_single_item (
222- k8s_client , data , verbose , namespace = namespace , ** kwargs )
240+ k8s_client , data , verbose , namespace = namespace , apply = apply , ** kwargs
241+ )
223242 k8s_objects .append (created )
224243 except client .rest .ApiException as api_exception :
225244 api_exceptions .append (api_exception )
@@ -232,7 +251,23 @@ def create_from_dict(k8s_client, data, verbose=False, namespace='default',
232251
233252
234253def create_from_yaml_single_item (
235- k8s_client , yml_object , verbose = False , ** kwargs ):
254+ k8s_client , yml_object , verbose = False , apply = False , ** kwargs
255+ ):
256+
257+ kind = yml_object ["kind" ]
258+ if apply is True :
259+ apply_client = DynamicClient (k8s_client ).resources .get (
260+ api_version = yml_object ["apiVersion" ], kind = kind
261+ )
262+ resp = apply_client .server_side_apply (
263+ body = yml_object , field_manager = "python-client" , ** kwargs
264+ )
265+ if verbose :
266+ msg = "{0} created." .format (kind )
267+ if hasattr (resp , "status" ):
268+ msg += " status='{0}'" .format (str (resp .status ))
269+ print (msg )
270+ return resp
236271 group , _ , version = yml_object ["apiVersion" ].partition ("/" )
237272 if version == "" :
238273 version = group
@@ -242,29 +277,30 @@ def create_from_yaml_single_item(
242277 group = "" .join (group .rsplit (".k8s.io" , 1 ))
243278 # convert group name from DNS subdomain format to
244279 # python class name convention
245- group = "" .join (word .capitalize () for word in group .split ('.' ))
280+ group = "" .join (word .capitalize () for word in group .split ("." ))
246281 fcn_to_call = "{0}{1}Api" .format (group , version .capitalize ())
247282 k8s_api = getattr (client , fcn_to_call )(k8s_client )
248283 # Replace CamelCased action_type into snake_case
249- kind = yml_object ["kind" ]
250- kind = UPPER_FOLLOWED_BY_LOWER_RE .sub (r'\1_\2' , kind )
251- kind = LOWER_OR_NUM_FOLLOWED_BY_UPPER_RE .sub (r'\1_\2' , kind ).lower ()
284+ kind = UPPER_FOLLOWED_BY_LOWER_RE .sub (r"\1_\2" , kind )
285+ kind = LOWER_OR_NUM_FOLLOWED_BY_UPPER_RE .sub (r"\1_\2" , kind ).lower ()
252286 # Expect the user to create namespaced objects more often
253287 if hasattr (k8s_api , "create_namespaced_{0}" .format (kind )):
254288 # Decide which namespace we are going to put the object in,
255289 # if any
256290 if "namespace" in yml_object ["metadata" ]:
257291 namespace = yml_object ["metadata" ]["namespace" ]
258- kwargs [' namespace' ] = namespace
292+ kwargs [" namespace" ] = namespace
259293 resp = getattr (k8s_api , "create_namespaced_{0}" .format (kind ))(
260- body = yml_object , ** kwargs )
294+ body = yml_object , ** kwargs
295+ )
261296 else :
262- kwargs .pop (' namespace' , None )
297+ kwargs .pop (" namespace" , None )
263298 resp = getattr (k8s_api , "create_{0}" .format (kind ))(
264- body = yml_object , ** kwargs )
299+ body = yml_object , ** kwargs
300+ )
265301 if verbose :
266302 msg = "{0} created." .format (kind )
267- if hasattr (resp , ' status' ):
303+ if hasattr (resp , " status" ):
268304 msg += " status='{0}'" .format (str (resp .status ))
269305 print (msg )
270306 return resp
@@ -283,5 +319,6 @@ def __str__(self):
283319 msg = ""
284320 for api_exception in self .api_exceptions :
285321 msg += "Error from server ({0}): {1}" .format (
286- api_exception .reason , api_exception .body )
322+ api_exception .reason , api_exception .body
323+ )
287324 return msg
0 commit comments