1
- # Copyright 2018 The Kubernetes Authors.
1
+ # Copyright 2019 The Kubernetes Authors.
2
2
#
3
3
# Licensed under the Apache License, Version 2.0 (the "License");
4
4
# you may not use this file except in compliance with the License.
13
13
# limitations under the License.
14
14
15
15
16
- import re
17
16
import os
17
+ import re
18
18
19
19
import yaml
20
-
21
20
from kubernetes import client
21
+ from kubernetes .dynamic .client import DynamicClient
22
22
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])" )
25
25
26
26
27
27
def 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
+ ):
33
30
"""
34
31
Perform an action from files from a directory. Pass True for verbose to
35
32
print confirmation information.
@@ -44,6 +41,7 @@ def create_from_directory(
44
41
the resource creation will fail. If the API object in
45
42
the yaml file already contains a namespace definition
46
43
this parameter has no effect.
44
+ apply: bool. If True, use server-side apply for creating resources.
47
45
48
46
Available parameters for creating <kind>:
49
47
:param async_req bool
@@ -65,27 +63,31 @@ def create_from_directory(
65
63
"""
66
64
67
65
if not yaml_dir :
68
- raise ValueError (
69
- '`yaml_dir` argument must be provided' )
66
+ raise ValueError ("`yaml_dir` argument must be provided" )
70
67
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" )
73
69
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
+ ]
76
75
if not files :
77
- raise ValueError (
78
- '`yaml_dir` contains no files' )
76
+ raise ValueError ("`yaml_dir` contains no files" )
79
77
80
78
failures = []
81
79
k8s_objects_all = []
82
80
83
81
for file in files :
84
82
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
+ )
89
91
k8s_objects_all .append (k8s_objects )
90
92
except FailToCreateError as failure :
91
93
failures .extend (failure .api_exceptions )
@@ -95,12 +97,14 @@ def create_from_directory(
95
97
96
98
97
99
def 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
+ ):
104
108
"""
105
109
Perform an action from a yaml file. Pass True for verbose to
106
110
print confirmation information.
@@ -116,6 +120,7 @@ def create_from_yaml(
116
120
the resource creation will fail. If the API object in
117
121
the yaml file already contains a namespace definition
118
122
this parameter has no effect.
123
+ apply: bool. If True, use server-side apply for creating resources.
119
124
120
125
Available parameters for creating <kind>:
121
126
:param async_req bool
@@ -136,16 +141,21 @@ def create_from_yaml(
136
141
instances for each object that failed to create.
137
142
"""
138
143
139
- def create_with (objects ):
144
+ def create_with (objects , apply = apply ):
140
145
failures = []
141
146
k8s_objects = []
142
147
for yml_document in objects :
143
148
if yml_document is None :
144
149
continue
145
150
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
+ )
149
159
k8s_objects .append (created )
150
160
except FailToCreateError as failure :
151
161
failures .extend (failure .api_exceptions )
@@ -164,14 +174,16 @@ class Loader(yaml.loader.SafeLoader):
164
174
elif yaml_file :
165
175
with open (os .path .abspath (yaml_file )) as f :
166
176
yml_document_all = yaml .load_all (f , Loader = Loader )
167
- return create_with (yml_document_all )
177
+ return create_with (yml_document_all , apply )
168
178
else :
169
179
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
+ )
171
182
172
183
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
+ ):
175
187
"""
176
188
Perform an action from a dictionary containing valid kubernetes
177
189
API object (i.e. List, Service, etc).
@@ -186,6 +198,7 @@ def create_from_dict(k8s_client, data, verbose=False, namespace='default',
186
198
the resource creation will fail. If the API object in
187
199
the yaml file already contains a namespace definition
188
200
this parameter has no effect.
201
+ apply: bool. If True, use server-side apply for creating resources.
189
202
190
203
Returns:
191
204
The created kubernetes API objects.
@@ -210,16 +223,22 @@ def create_from_dict(k8s_client, data, verbose=False, namespace='default',
210
223
yml_object ["kind" ] = kind
211
224
try :
212
225
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
+ )
215
233
k8s_objects .append (created )
216
234
except client .rest .ApiException as api_exception :
217
235
api_exceptions .append (api_exception )
218
236
else :
219
237
# This is a single object. Call the single item method
220
238
try :
221
239
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
+ )
223
242
k8s_objects .append (created )
224
243
except client .rest .ApiException as api_exception :
225
244
api_exceptions .append (api_exception )
@@ -232,7 +251,23 @@ def create_from_dict(k8s_client, data, verbose=False, namespace='default',
232
251
233
252
234
253
def 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
236
271
group , _ , version = yml_object ["apiVersion" ].partition ("/" )
237
272
if version == "" :
238
273
version = group
@@ -242,29 +277,30 @@ def create_from_yaml_single_item(
242
277
group = "" .join (group .rsplit (".k8s.io" , 1 ))
243
278
# convert group name from DNS subdomain format to
244
279
# python class name convention
245
- group = "" .join (word .capitalize () for word in group .split ('.' ))
280
+ group = "" .join (word .capitalize () for word in group .split ("." ))
246
281
fcn_to_call = "{0}{1}Api" .format (group , version .capitalize ())
247
282
k8s_api = getattr (client , fcn_to_call )(k8s_client )
248
283
# 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 ()
252
286
# Expect the user to create namespaced objects more often
253
287
if hasattr (k8s_api , "create_namespaced_{0}" .format (kind )):
254
288
# Decide which namespace we are going to put the object in,
255
289
# if any
256
290
if "namespace" in yml_object ["metadata" ]:
257
291
namespace = yml_object ["metadata" ]["namespace" ]
258
- kwargs [' namespace' ] = namespace
292
+ kwargs [" namespace" ] = namespace
259
293
resp = getattr (k8s_api , "create_namespaced_{0}" .format (kind ))(
260
- body = yml_object , ** kwargs )
294
+ body = yml_object , ** kwargs
295
+ )
261
296
else :
262
- kwargs .pop (' namespace' , None )
297
+ kwargs .pop (" namespace" , None )
263
298
resp = getattr (k8s_api , "create_{0}" .format (kind ))(
264
- body = yml_object , ** kwargs )
299
+ body = yml_object , ** kwargs
300
+ )
265
301
if verbose :
266
302
msg = "{0} created." .format (kind )
267
- if hasattr (resp , ' status' ):
303
+ if hasattr (resp , " status" ):
268
304
msg += " status='{0}'" .format (str (resp .status ))
269
305
print (msg )
270
306
return resp
@@ -283,5 +319,6 @@ def __str__(self):
283
319
msg = ""
284
320
for api_exception in self .api_exceptions :
285
321
msg += "Error from server ({0}): {1}" .format (
286
- api_exception .reason , api_exception .body )
322
+ api_exception .reason , api_exception .body
323
+ )
287
324
return msg
0 commit comments