1 | # |
---|
2 | # Licensed under the GNU General Public License Version 3 |
---|
3 | # |
---|
4 | # This program is free software; you can redistribute it and/or modify |
---|
5 | # it under the terms of the GNU General Public License as published by |
---|
6 | # the Free Software Foundation; either version 3 of the License, or |
---|
7 | # (at your option) any later version. |
---|
8 | # |
---|
9 | # This program is distributed in the hope that it will be useful, |
---|
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
---|
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
---|
12 | # GNU General Public License for more details. |
---|
13 | # |
---|
14 | # You should have received a copy of the GNU General Public License |
---|
15 | # along with this program; if not, write to the Free Software |
---|
16 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
---|
17 | # |
---|
18 | # Copyright 2011,2012 Joerg Steffens <joerg.steffens@dass-it.de> |
---|
19 | # |
---|
20 | |
---|
21 | # NOTE: the 'self' variable is an instance of SpacewalkShell |
---|
22 | |
---|
23 | import shlex |
---|
24 | from optparse import Option |
---|
25 | from pprint import pprint |
---|
26 | import sys |
---|
27 | from StringIO import StringIO |
---|
28 | from tempfile import mkdtemp |
---|
29 | import difflib |
---|
30 | from spacecmd.utils import * |
---|
31 | |
---|
32 | _STAGE1='dev' |
---|
33 | _STAGE2='stg' |
---|
34 | _STAGE3='prd' |
---|
35 | |
---|
36 | _STAGES=[ _STAGE1, _STAGE2, _STAGE3 ] |
---|
37 | |
---|
38 | _STAGE_NAMES={ |
---|
39 | 'dev': 'Development', |
---|
40 | # alternative |
---|
41 | 'qas': 'QualityAssurance', |
---|
42 | 'stg': 'Staging', |
---|
43 | 'prd': 'Production' |
---|
44 | } |
---|
45 | _STAGE_TRANSITIONS={ |
---|
46 | _STAGE1: _STAGE2, |
---|
47 | _STAGE2: _STAGE3 |
---|
48 | } |
---|
49 | _STAGE_STATUS={ |
---|
50 | "uptodate": " ", |
---|
51 | "modified": "M", |
---|
52 | "dontexist": "!", |
---|
53 | "unknown": "?" |
---|
54 | } |
---|
55 | # when normalizing texts, exclude lines that start with following keywords, |
---|
56 | # because they result in differences |
---|
57 | # that are not relevant for our staging diffs |
---|
58 | _STAGE_TEXT_EXCLUDES=[ |
---|
59 | # configchannel -> file details |
---|
60 | "Revision: ", "Created: ", "Modified: ", |
---|
61 | # kickstart |
---|
62 | "Org Default: ", |
---|
63 | # activation key |
---|
64 | "Universal Default: ", |
---|
65 | ] |
---|
66 | |
---|
67 | _DUMP_BASE_DIR="/tmp/spacecmd-stage-dump/" |
---|
68 | |
---|
69 | #################### |
---|
70 | |
---|
71 | def do_stage_help( self, args, doreturn = False ): |
---|
72 | print """ |
---|
73 | Staging: |
---|
74 | |
---|
75 | The basic principle is to have every component in multiple stages. |
---|
76 | The stages in this environment are:""" |
---|
77 | for stage in _STAGES: |
---|
78 | successor=self.get_next_stage(stage) |
---|
79 | print " " + stage + ":" , _STAGE_NAMES.get(stage) |
---|
80 | |
---|
81 | print """ |
---|
82 | A stage can have a successor, in our enviroment these are:""" |
---|
83 | for stage in _STAGES: |
---|
84 | successor=self.get_next_stage(stage) |
---|
85 | if successor: |
---|
86 | print " " + stage, "->" , successor |
---|
87 | |
---|
88 | print """ |
---|
89 | Workflow example: |
---|
90 | * creating a new package/package version |
---|
91 | the new package is added to a {stage1} softwarechannel. |
---|
92 | If the package seams to work correctly, |
---|
93 | a new integration phase can be started. |
---|
94 | For this, the packages are copied from {stage1} to {stage2}. |
---|
95 | The {stage2} stage is then tested. |
---|
96 | After a successful test, all content of {stage2} is transfered to {stage3}. |
---|
97 | When the content has arrived in {stage3}, |
---|
98 | all productively used systems are able to update to the new content. |
---|
99 | |
---|
100 | Summary: |
---|
101 | {stage1}: all changes are done to {stage1} |
---|
102 | {stage2}: integration tests are done in {stage2} only |
---|
103 | {stage3}: productively used systems using {stage3} only |
---|
104 | |
---|
105 | Changes are not only adding new packages, |
---|
106 | but also changing configuration files in the configuration channels, |
---|
107 | changing kickstart settings or changing activation keys. |
---|
108 | |
---|
109 | For all these changes, spacecmd stage_* commands offers functionality |
---|
110 | to simplify staging. |
---|
111 | |
---|
112 | Usage: |
---|
113 | * create your channels, actionvationkey and so on. |
---|
114 | Because Spacewalk does not know about staging directly, |
---|
115 | staging information must be coded into the name of the components. |
---|
116 | The name must include the stage, separeted by '-', |
---|
117 | eg. centos6-x86_64-{stage1}, centos6-x86_64-{stage1}-subchannel, ks-centos6-x86_64-{stage1}-common, ... |
---|
118 | To create a initial structure, the comamnd 'stage_create_skel' can be used. |
---|
119 | |
---|
120 | * check the staging status by 'stage_status STAGE' |
---|
121 | This will select all components from stage 'STAGE' and compare each component with the correcponding component from the successor stage, eg.: |
---|
122 | 'stage_status {stage1}' |
---|
123 | INFO: softwarechannel |
---|
124 | centos6-x86_64-{stage1} -> centos6-x86_64-{stage2} |
---|
125 | M centos6-x86_64-{stage1}-app1 -> centos6-x86_64-{stage2}-app1 |
---|
126 | ! centos6-x86_64-{stage1}-app2 |
---|
127 | INFO: configchannel |
---|
128 | cfg-centos6-x86_64-{stage1}-app1 -> cfg-centos6-x86_64-{stage2}-app1 |
---|
129 | INFO: kickstart |
---|
130 | M ks-centos6-x86_64-{stage1}-app1 -> ks-centos6-x86_64-{stage2}-app1 |
---|
131 | INFO: activationkey |
---|
132 | 1-centos6-x86_64-{stage1}-app1 -> 1-centos6-x86_64-{stage2}-app1 |
---|
133 | |
---|
134 | This first column indicates the state: |
---|
135 | : empty: no differences. The components from both stages are indentical |
---|
136 | ! : no correcponding component in successor stage found |
---|
137 | M : modification. The component differs between the current and the successor stage |
---|
138 | |
---|
139 | * The most interessting entries are the modified entires. |
---|
140 | To check this more specifically, use the corresponding 'stage_*_diff' function, eg. |
---|
141 | 'stage_softwarechannel_diff centos7-x86_64-{stage1}-app1' |
---|
142 | --- centos6-x86_64-{stage1}-app1 |
---|
143 | |
---|
144 | +++ centos6-x86_64-{stage2}-app1 |
---|
145 | |
---|
146 | @@ -1,1 +1,0 @@ |
---|
147 | |
---|
148 | -newpackage-1.0.1-1.1.noarch |
---|
149 | |
---|
150 | (it is also possible to compare two specific subchannel, eg. |
---|
151 | 'stage_softwarechannel_diff centos6-x86_64-{stage1}-subchannel1 centos6-x86_64-{stage2}-subchannel1' |
---|
152 | but the corresponding successor stage component is found automatically by its name) |
---|
153 | |
---|
154 | * Softwarechannel and configchannel also offers the stage_*_sync function. |
---|
155 | Use them, to copy the content of a component to the next stage, e.g. |
---|
156 | 'stage_softwarechannel_sync centos6-x86_64-{stage1}-app1' |
---|
157 | INFO: syncing packages from softwarechannel centos6-x86_64-{stage1}-app1 to centos6-x86_64-{stage2}-app1 |
---|
158 | packages to add to channel "centos6-x86_64-{stage2}-app1": |
---|
159 | newpackage-1.0.1-1.1.noarch |
---|
160 | Perform these changes to channel centos6-x86_64-{stage2}-app1 [y/N]: |
---|
161 | |
---|
162 | * Repeat these steps, until 'stage_status STAGE' shows no differences between the two stages |
---|
163 | """.format(stage1=_STAGE1, stage2=_STAGE2, stage3=_STAGE3) |
---|
164 | |
---|
165 | def help_stage_create_skel(self): |
---|
166 | print 'stage_create_skel: create initial staging structure' |
---|
167 | print '''usage: stage_create_skel [options] |
---|
168 | |
---|
169 | options: |
---|
170 | -l LABEL |
---|
171 | -a ARCHITECTURE ['ia32', 'x86_64'] |
---|
172 | -s SUB (e.g. application1)''' |
---|
173 | |
---|
174 | def do_stage_create_skel(self, args, doreturn = False): |
---|
175 | options = [ |
---|
176 | Option('-l', '--label', action='store'), |
---|
177 | Option('-a', '--arch', action='store'), |
---|
178 | Option('-s', '--sub', action='store'), |
---|
179 | ] |
---|
180 | |
---|
181 | (args, options) = parse_arguments(args, options) |
---|
182 | |
---|
183 | if is_interactive(options): |
---|
184 | options.label = prompt_user('Channel Label:', noblank = True) |
---|
185 | |
---|
186 | print |
---|
187 | print 'Architecture' |
---|
188 | print '------------' |
---|
189 | print '\n'.join(sorted(self.ARCH_LABELS)) |
---|
190 | print |
---|
191 | options.arch = prompt_user('Select:') |
---|
192 | options.arch = prompt_user('Sub:') |
---|
193 | else: |
---|
194 | if not options.label: |
---|
195 | logging.error('A channel label is required') |
---|
196 | return |
---|
197 | |
---|
198 | if not options.arch: |
---|
199 | logging.error('An architecture is required') |
---|
200 | return |
---|
201 | |
---|
202 | dist = options.label |
---|
203 | arch = options.arch |
---|
204 | if self.stage_create_skel( options.label, options.arch, options.sub, create=False ): |
---|
205 | self.stage_create_skel( options.label, options.arch, options.sub, create=True ) |
---|
206 | |
---|
207 | |
---|
208 | def stage_create_skel(self, dist, arch, sub, create = False): |
---|
209 | |
---|
210 | org = "1" |
---|
211 | disttype = "rhel_6" |
---|
212 | application = sub |
---|
213 | |
---|
214 | print |
---|
215 | for stage in _STAGES: |
---|
216 | base = dist + "-" + arch + "-" + stage |
---|
217 | softwarechannel_base = base |
---|
218 | softwarechannel_sub = base + "-" + application |
---|
219 | distribution = "dist-" + base |
---|
220 | distributionpath = "/srv/dist/" + base |
---|
221 | configchannel = "cfg-" + base + "-" + application |
---|
222 | activationkey_create = base + "-" + application |
---|
223 | activationkey = org + "-" + activationkey_create |
---|
224 | kickstart = "ks-" + base + "-" + application |
---|
225 | |
---|
226 | print "stage: " + stage |
---|
227 | |
---|
228 | print "softwarechannel base: " + softwarechannel_base, |
---|
229 | if self.is_softwarechannel( softwarechannel_base ): |
---|
230 | print " [exists]", |
---|
231 | elif create: |
---|
232 | self.do_softwarechannel_create( "-n " + softwarechannel_base + " -l " + softwarechannel_base + " -a " + arch ) |
---|
233 | print |
---|
234 | |
---|
235 | print "softwarechannel subchannel: " + softwarechannel_sub, |
---|
236 | if self.is_softwarechannel( softwarechannel_sub ): |
---|
237 | print " [exists]", |
---|
238 | elif create: |
---|
239 | self.do_softwarechannel_create( "-n " + softwarechannel_sub + " -l " + softwarechannel_sub + " -a " + arch + " -p " + base ) |
---|
240 | print |
---|
241 | |
---|
242 | |
---|
243 | print "distribution: " + distribution + " (distribution path: " + distributionpath + ")", |
---|
244 | if distribution in self.do_distribution_list(distribution, True): |
---|
245 | print " [exists]", |
---|
246 | elif create: |
---|
247 | self.do_distribution_create( "--name " + distribution + " --path " + distributionpath + " --base-channel " + base + " --install-type rhel_6" ) |
---|
248 | print |
---|
249 | |
---|
250 | print "configchannel: " + configchannel, |
---|
251 | if self.is_configchannel( configchannel ): |
---|
252 | print " [exists]", |
---|
253 | elif create: |
---|
254 | self.do_configchannel_create( "-n " + configchannel ) |
---|
255 | print |
---|
256 | |
---|
257 | print "activationkey: " + activationkey, |
---|
258 | if self.is_activationkey( activationkey ): |
---|
259 | print " [exists]", |
---|
260 | elif create: |
---|
261 | self.do_activationkey_create( "-n " + activationkey_create + " -d " + activationkey + " -b " + base + " -e provisioning_entitled" ) |
---|
262 | self.do_activationkey_addchildchannels( activationkey + " " + softwarechannel_sub ) |
---|
263 | self.do_activationkey_enableconfigdeployment( activationkey ) |
---|
264 | self.do_activationkey_addconfigchannels( activationkey + " " + configchannel + " -t" ) |
---|
265 | print |
---|
266 | |
---|
267 | print "kickstart: " + kickstart, |
---|
268 | if self.is_kickstart( kickstart ): |
---|
269 | print " [exists]", |
---|
270 | elif create: |
---|
271 | self.do_kickstart_create( "--name=" + kickstart + " --distribution=" + distribution + " --root-password=CHANGEME --virt-type=none" ) |
---|
272 | self.do_kickstart_addactivationkeys( kickstart + " " + activationkey ) |
---|
273 | self.do_kickstart_enableconfigmanagement( kickstart ) |
---|
274 | self.do_kickstart_enablelogging( kickstart ) |
---|
275 | print |
---|
276 | |
---|
277 | print |
---|
278 | |
---|
279 | if not create: |
---|
280 | print "Make sure, distribution trees are available at the specified distribution paths." |
---|
281 | return self.user_confirm('Create this components [y/N]:') |
---|
282 | |
---|
283 | |
---|
284 | |
---|
285 | #################### |
---|
286 | |
---|
287 | # |
---|
288 | # helper functions |
---|
289 | # |
---|
290 | |
---|
291 | def is_stage( self, name ): |
---|
292 | return name in _STAGES |
---|
293 | |
---|
294 | def check_stage( self, name ): |
---|
295 | """Checks if name describes a vaild stage""" |
---|
296 | if not name: |
---|
297 | logging.error( "no stage given" ) |
---|
298 | return False |
---|
299 | if not self.is_stage( name ): |
---|
300 | logging.error( "invalid stage " + name ) |
---|
301 | return False |
---|
302 | return True |
---|
303 | |
---|
304 | def is_current_stage(self, name): |
---|
305 | return "-"+self.stage in name |
---|
306 | |
---|
307 | def get_common_name( self, name ): |
---|
308 | """Returns the name with the stage replaced by 'STAGE' |
---|
309 | |
---|
310 | To check the differences from 2 components that are in different stages, |
---|
311 | the specific stage is replaced by the word 'STAGE' |
---|
312 | """ |
---|
313 | return self.replace_stage_in_name( name, self.stage, "STAGE" ) |
---|
314 | |
---|
315 | |
---|
316 | def get_stage_from_name( self, name ): |
---|
317 | for i in _STAGES: |
---|
318 | if "-"+i in name: |
---|
319 | return i |
---|
320 | |
---|
321 | def get_next_stage( self, current_stage ): |
---|
322 | return _STAGE_TRANSITIONS.get(current_stage) |
---|
323 | |
---|
324 | def replace_stage_in_name( self, name, current_stage, new_stage ): |
---|
325 | """Return the name with current stage replaced by new stage""" |
---|
326 | return name.replace( "-"+current_stage, "-"+new_stage ) |
---|
327 | |
---|
328 | def get_next_stage_name( self, name ): |
---|
329 | current_stage = self.get_stage_from_name( name ) |
---|
330 | if not current_stage: return |
---|
331 | next_stage = self.get_next_stage( current_stage ) |
---|
332 | if not next_stage: return |
---|
333 | next_stage_name = self.replace_stage_in_name( name, current_stage, next_stage ) |
---|
334 | return next_stage_name |
---|
335 | |
---|
336 | def get_normalized_text( self, text, stage, excludes=_STAGE_TEXT_EXCLUDES ): |
---|
337 | """Replace all occurances of stage by the word 'STAGE'""" |
---|
338 | normalized_text = [] |
---|
339 | for line in text: |
---|
340 | if not line.startswith( tuple(excludes) ): |
---|
341 | normalized_text.append( self.replace_stage_in_name( line, stage, "STAGE" ) ) |
---|
342 | return normalized_text |
---|
343 | |
---|
344 | def print_stage_status( self, name, name_next=None, status="unknown", indent="" ): |
---|
345 | width=48-len(indent) |
---|
346 | string = '{status_code} {indent}{name:{width}}'.format(status_code=_STAGE_STATUS.get(status), indent=indent, name=name, width=width ) |
---|
347 | if name_next: |
---|
348 | string = string + " -> " + indent + name_next |
---|
349 | print string |
---|
350 | |
---|
351 | def mkdir(self, name ): |
---|
352 | try: |
---|
353 | if not os.path.isdir( name ): |
---|
354 | os.makedirs( name ) |
---|
355 | logging.debug( "creating directory " + name ) |
---|
356 | return True |
---|
357 | except: |
---|
358 | logging.error('Failed to create directory ' + name ) |
---|
359 | return False |
---|
360 | |
---|
361 | def dump(self, filename, data, raw=False): |
---|
362 | """Writes data to filename""" |
---|
363 | if not self.mkdir( os.path.dirname( filename )): return False |
---|
364 | try: |
---|
365 | fh = open( filename, 'w' ) |
---|
366 | if( raw ): |
---|
367 | fh.write(data) |
---|
368 | else: |
---|
369 | fh.write("\n".join(data)) |
---|
370 | fh.close() |
---|
371 | except: |
---|
372 | logging.error('failed to create file ' + filename ) |
---|
373 | return False |
---|
374 | |
---|
375 | |
---|
376 | #################### |
---|
377 | |
---|
378 | # |
---|
379 | # softwarechannel |
---|
380 | # |
---|
381 | |
---|
382 | # softwarechannel helper |
---|
383 | |
---|
384 | def is_softwarechannel( self, name ): |
---|
385 | if not name: return |
---|
386 | return name in self.do_softwarechannel_list( name, True ) |
---|
387 | |
---|
388 | def check_softwarechannel( self, name ): |
---|
389 | if not name: |
---|
390 | logging.error( "no softwarechannel label given" ) |
---|
391 | return False |
---|
392 | if not self.is_softwarechannel( name ): |
---|
393 | logging.error( "invalid softwarechannel label " + name ) |
---|
394 | return False |
---|
395 | return True |
---|
396 | |
---|
397 | def get_softwarechannel_childchannel( self, base_channel ): |
---|
398 | result=[] |
---|
399 | for child_channel in self.list_child_channels(): |
---|
400 | details = self.client.channel.software.getDetails(\ |
---|
401 | self.session, child_channel) |
---|
402 | if details.get('parent_channel_label') == base_channel: |
---|
403 | result.append( child_channel ) |
---|
404 | return result |
---|
405 | |
---|
406 | |
---|
407 | # softwarechannel next |
---|
408 | |
---|
409 | def help_stage_softwarechannel_next(self): |
---|
410 | print 'stage_softwarechannel_next: get softwarechannel name for the next stage' |
---|
411 | print ' ' |
---|
412 | print 'usage: stage_softwarechannel_next CHANNEL' |
---|
413 | |
---|
414 | def complete_stage_softwarechannel_next(self, text, line, beg, end): |
---|
415 | parts = shlex.split(line) |
---|
416 | if line[-1] == ' ': parts.append('') |
---|
417 | args = len(parts) |
---|
418 | |
---|
419 | if args == 2: |
---|
420 | return tab_completer(self.do_softwarechannel_list('', True), text) |
---|
421 | return [] |
---|
422 | |
---|
423 | def do_stage_softwarechannel_next(self, args, doreturn = False): |
---|
424 | (args, options) = parse_arguments(args) |
---|
425 | |
---|
426 | if len(args) != 1: |
---|
427 | self.help_stage_softwarechannel_next() |
---|
428 | return |
---|
429 | |
---|
430 | source_name = args[0] |
---|
431 | if not self.is_softwarechannel(source_name): |
---|
432 | logging.warning( "invalid softwarechannel "+source_name ) |
---|
433 | return |
---|
434 | logging.debug( "source: " + str(source_name) ) |
---|
435 | target_name = self.get_next_stage_name( source_name ) |
---|
436 | logging.debug( "target: " + str(target_name) ) |
---|
437 | if not target_name: return |
---|
438 | # check target name |
---|
439 | if not self.is_softwarechannel(target_name): |
---|
440 | if not doreturn: |
---|
441 | logging.warning( "a next stage softwarechannel for "+source_name+" ("+target_name+") does not exist" ) |
---|
442 | return |
---|
443 | |
---|
444 | if doreturn: |
---|
445 | return target_name |
---|
446 | else: |
---|
447 | print target_name |
---|
448 | |
---|
449 | |
---|
450 | # softwarechannel diff |
---|
451 | |
---|
452 | def help_stage_softwarechannel_diff(self): |
---|
453 | print 'stage_softwarechannel_diff: diff softwarechannel files' |
---|
454 | print '' |
---|
455 | print 'usage: stage_softwarechannel_diff SOURCE_CHANNEL [TARGET_CHANNEL]' |
---|
456 | |
---|
457 | def complete_stage_softwarechannel_diff(self, text, line, beg, end): |
---|
458 | parts = shlex.split(line) |
---|
459 | if line[-1] == ' ': parts.append('') |
---|
460 | args = len(parts) |
---|
461 | |
---|
462 | if args == 2: |
---|
463 | return tab_completer(self.do_softwarechannel_list('', True), text) |
---|
464 | if args == 3: |
---|
465 | return tab_completer(self.do_softwarechannel_list('', True), text) |
---|
466 | return [] |
---|
467 | |
---|
468 | def do_stage_softwarechannel_diff(self, args, doreturn = False): |
---|
469 | options = [] |
---|
470 | |
---|
471 | (args, options) = parse_arguments(args, options) |
---|
472 | |
---|
473 | if len(args) != 1 and len(args) != 2: |
---|
474 | self.help_stage_softwarechannel_diff() |
---|
475 | return |
---|
476 | |
---|
477 | source_channel = args[0] |
---|
478 | if not self.check_softwarechannel( source_channel ): return |
---|
479 | |
---|
480 | if len(args) == 2: |
---|
481 | target_channel = args[1] |
---|
482 | else: |
---|
483 | target_channel=self.do_stage_softwarechannel_next( source_channel, doreturn = True) |
---|
484 | if not self.check_softwarechannel( target_channel ): return |
---|
485 | |
---|
486 | |
---|
487 | source_data = self.dump_softwarechannel( source_channel, normalize=True ) |
---|
488 | target_data = self.dump_softwarechannel( target_channel, normalize=True ) |
---|
489 | |
---|
490 | result=[] |
---|
491 | |
---|
492 | for line in difflib.unified_diff( source_data, target_data, source_channel, target_channel ): |
---|
493 | if doreturn: |
---|
494 | result.append(line) |
---|
495 | else: |
---|
496 | print line |
---|
497 | return result |
---|
498 | |
---|
499 | |
---|
500 | |
---|
501 | # softwarechannel sync |
---|
502 | |
---|
503 | def help_stage_softwarechannel_sync(self): |
---|
504 | print 'stage_softwarechannel_sync: sync the (most recent) packages' |
---|
505 | print ' from a software channel to another' |
---|
506 | print 'usage: stage_softwarechannel_sync SOURCE_CHANNEL [TARGET_CHANNEL]' |
---|
507 | |
---|
508 | def complete_stage_softwarechannel_sync(self, text, line, beg, end): |
---|
509 | parts = shlex.split(line) |
---|
510 | if line[-1] == ' ': parts.append('') |
---|
511 | args = len(parts) |
---|
512 | |
---|
513 | |
---|
514 | if args == 2: |
---|
515 | return tab_completer(self.do_softwarechannel_list('', True), text) |
---|
516 | if args == 3: |
---|
517 | return tab_completer(self.do_softwarechannel_list('', True), text) |
---|
518 | return [] |
---|
519 | |
---|
520 | def do_stage_softwarechannel_sync(self, args): |
---|
521 | options = [] |
---|
522 | |
---|
523 | (args, options) = parse_arguments(args, options) |
---|
524 | |
---|
525 | if len(args) != 1 and len(args) != 2: |
---|
526 | self.help_stage_softwarechannel_sync() |
---|
527 | return |
---|
528 | |
---|
529 | source_channel = args[0] |
---|
530 | if len(args) == 2: |
---|
531 | target_channel = args[1] |
---|
532 | else: |
---|
533 | target_channel=self.do_stage_softwarechannel_next( source_channel, doreturn = True) |
---|
534 | |
---|
535 | logging.info( "syncing packages from softwarechannel "+source_channel+" to "+target_channel ) |
---|
536 | |
---|
537 | # use API call instead of spacecmd function |
---|
538 | # to get detailed infos about the packages |
---|
539 | # and not just there names |
---|
540 | source_packages = self.client.channel.software.listAllPackages(self.session, |
---|
541 | source_channel) |
---|
542 | target_packages = self.client.channel.software.listAllPackages(self.session, |
---|
543 | target_channel) |
---|
544 | |
---|
545 | |
---|
546 | # get the package IDs |
---|
547 | source_package_ids = set() |
---|
548 | for package in source_packages: |
---|
549 | try: |
---|
550 | source_package_ids.add(package['id']) |
---|
551 | except KeyError: |
---|
552 | logging.error( "failed to read key id" ) |
---|
553 | continue |
---|
554 | |
---|
555 | target_package_ids = set() |
---|
556 | for package in target_packages: |
---|
557 | try: |
---|
558 | target_package_ids.add(package['id']) |
---|
559 | except KeyError: |
---|
560 | logging.error( "failed to read key id" ) |
---|
561 | continue |
---|
562 | |
---|
563 | print "packages common in both channels:" |
---|
564 | for i in ( source_package_ids & target_package_ids ): |
---|
565 | print self.get_package_name( i ) |
---|
566 | print |
---|
567 | |
---|
568 | source_only = source_package_ids.difference(target_package_ids) |
---|
569 | if source_only: |
---|
570 | print 'packages to add to channel "' + target_channel + '":' |
---|
571 | for i in source_only: |
---|
572 | print self.get_package_name( i ) |
---|
573 | print |
---|
574 | |
---|
575 | |
---|
576 | # check for packages only in target |
---|
577 | target_only=target_package_ids.difference( source_package_ids ) |
---|
578 | if target_only: |
---|
579 | print 'packages to remove from channel "' + target_channel + '":' |
---|
580 | for i in target_only: |
---|
581 | print self.get_package_name( i ) |
---|
582 | print |
---|
583 | |
---|
584 | if source_only or target_only: |
---|
585 | if not self.user_confirm('Perform these changes to channel ' + target_channel + ' [y/N]:'): return |
---|
586 | |
---|
587 | self.client.channel.software.addPackages(self.session, |
---|
588 | target_channel, |
---|
589 | list(source_only) ) |
---|
590 | self.client.channel.software.removePackages(self.session, |
---|
591 | target_channel, |
---|
592 | list(target_only) ) |
---|
593 | |
---|
594 | #################### |
---|
595 | |
---|
596 | # |
---|
597 | # configchannel |
---|
598 | # |
---|
599 | |
---|
600 | # configchannel helper |
---|
601 | |
---|
602 | def is_configchannel( self, name ): |
---|
603 | if not name: return |
---|
604 | return name in self.do_configchannel_list( name, True ) |
---|
605 | |
---|
606 | def check_configchannel( self, name ): |
---|
607 | if not name: |
---|
608 | logging.error( "no configchannel given" ) |
---|
609 | return False |
---|
610 | if not self.is_configchannel( name ): |
---|
611 | logging.error( "invalid configchannel label " + name ) |
---|
612 | return False |
---|
613 | return True |
---|
614 | |
---|
615 | |
---|
616 | # configchannel next |
---|
617 | |
---|
618 | def help_stage_configchannel_next(self): |
---|
619 | print 'stage_configchannel_next: get configchannel name for the next stage' |
---|
620 | print ' ' |
---|
621 | print 'usage: stage_configchannel_next CHANNEL' |
---|
622 | |
---|
623 | def complete_stage_configchannel_next(self, text, line, beg, end): |
---|
624 | parts = shlex.split(line) |
---|
625 | if line[-1] == ' ': parts.append('') |
---|
626 | args = len(parts) |
---|
627 | |
---|
628 | if args == 2: |
---|
629 | return tab_completer(self.do_configchannel_list('', True), text) |
---|
630 | return [] |
---|
631 | |
---|
632 | def do_stage_configchannel_next(self, args, doreturn = False): |
---|
633 | (args, options) = parse_arguments(args) |
---|
634 | |
---|
635 | if len(args) != 1: |
---|
636 | self.help_stage_configchannel_next() |
---|
637 | return |
---|
638 | |
---|
639 | source_name = args[0] |
---|
640 | if not self.is_configchannel(source_name): |
---|
641 | logging.warning( "invalid configchannel "+source_name ) |
---|
642 | return |
---|
643 | logging.debug( "source: " + str(source_name) ) |
---|
644 | target_name = self.get_next_stage_name( source_name ) |
---|
645 | logging.debug( "target: " + str(target_name) ) |
---|
646 | if not target_name: return |
---|
647 | # check target name |
---|
648 | if not self.is_configchannel(target_name): |
---|
649 | if not doreturn: |
---|
650 | logging.warning( "a next stage configchannel for "+source_name+" ("+target_name+") does not exist" ) |
---|
651 | return |
---|
652 | |
---|
653 | if doreturn: |
---|
654 | return target_name |
---|
655 | else: |
---|
656 | print target_name |
---|
657 | |
---|
658 | |
---|
659 | # configchannel diff |
---|
660 | |
---|
661 | def help_stage_configchannel_diff(self): |
---|
662 | print 'stage_configchannel_diff: diff between config channels' |
---|
663 | print ' ' |
---|
664 | print 'usage: stage_configchannel_diff SOURCE_CHANNEL [TARGET_CHANNEL]' |
---|
665 | |
---|
666 | def complete_stage_configchannel_diff(self, text, line, beg, end): |
---|
667 | parts = shlex.split(line) |
---|
668 | if line[-1] == ' ': parts.append('') |
---|
669 | args = len(parts) |
---|
670 | |
---|
671 | if args == 2: |
---|
672 | return tab_completer(self.do_configchannel_list('', True), text) |
---|
673 | if args == 3: |
---|
674 | return tab_completer(self.do_configchannel_list('', True), text) |
---|
675 | return [] |
---|
676 | |
---|
677 | def do_stage_configchannel_diff(self, args, doreturn = False): |
---|
678 | options = [] |
---|
679 | |
---|
680 | (args, options) = parse_arguments(args, options) |
---|
681 | |
---|
682 | if len(args) != 1 and len(args) != 2: |
---|
683 | self.help_stage_configchannel_diff() |
---|
684 | return |
---|
685 | |
---|
686 | source_channel = args[0] |
---|
687 | if not self.check_configchannel( source_channel ): return |
---|
688 | |
---|
689 | if len(args) == 2: |
---|
690 | target_channel = args[1] |
---|
691 | else: |
---|
692 | target_channel=self.do_stage_configchannel_next( source_channel, doreturn = True) |
---|
693 | if not self.check_configchannel( target_channel ): return |
---|
694 | |
---|
695 | source_data = self.dump_configchannel( source_channel, normalize=True ) |
---|
696 | target_data = self.dump_configchannel( target_channel, normalize=True ) |
---|
697 | |
---|
698 | result=[] |
---|
699 | for line in difflib.unified_diff( source_data, target_data, source_channel, target_channel ): |
---|
700 | if doreturn: |
---|
701 | result.append(line) |
---|
702 | else: |
---|
703 | print line |
---|
704 | return result |
---|
705 | |
---|
706 | |
---|
707 | # configchannel sync |
---|
708 | |
---|
709 | def help_stage_configchannel_sync(self): |
---|
710 | print 'stage_configchannel_sync: sync config files' |
---|
711 | print ' from a config channel to another' |
---|
712 | print 'usage: stage_configchannel_sync SOURCE_CHANNEL [TARGET_CHANNEL]' |
---|
713 | |
---|
714 | def complete_stage_configchannel_sync(self, text, line, beg, end): |
---|
715 | parts = shlex.split(line) |
---|
716 | if line[-1] == ' ': parts.append('') |
---|
717 | args = len(parts) |
---|
718 | |
---|
719 | |
---|
720 | if args == 2: |
---|
721 | return tab_completer(self.do_configchannel_list('', True), text) |
---|
722 | if args == 3: |
---|
723 | return tab_completer(self.do_configchannel_list('', True), text) |
---|
724 | return [] |
---|
725 | |
---|
726 | def do_stage_configchannel_sync(self, args, doreturn = False): |
---|
727 | options = [] |
---|
728 | |
---|
729 | (args, options) = parse_arguments(args, options) |
---|
730 | |
---|
731 | if len(args) != 1 and len(args) != 2: |
---|
732 | self.help_stage_configchannel_sync() |
---|
733 | return |
---|
734 | |
---|
735 | source_channel = args[0] |
---|
736 | if not self.check_configchannel( source_channel ): return |
---|
737 | |
---|
738 | if len(args) == 2: |
---|
739 | target_channel = args[1] |
---|
740 | else: |
---|
741 | target_channel=self.do_stage_configchannel_next( source_channel, doreturn = True) |
---|
742 | if not self.check_configchannel( target_channel ): return |
---|
743 | |
---|
744 | logging.info( "syncing files from configchannel "+source_channel+" to "+target_channel ) |
---|
745 | |
---|
746 | source_files = set( self.do_configchannel_listfiles( source_channel, doreturn = True ) ) |
---|
747 | target_files = set( self.do_configchannel_listfiles( target_channel, doreturn = True ) ) |
---|
748 | |
---|
749 | both=source_files & target_files |
---|
750 | if both: |
---|
751 | print "files common in both channels:" |
---|
752 | print "\n".join( both ) |
---|
753 | print |
---|
754 | |
---|
755 | source_only=source_files.difference( target_files ) |
---|
756 | if source_only: |
---|
757 | print "files only in source "+source_channel |
---|
758 | print "\n".join( source_only ) |
---|
759 | print |
---|
760 | |
---|
761 | target_only=target_files.difference( source_files ) |
---|
762 | if target_only: |
---|
763 | print "files only in target "+target_channel |
---|
764 | print "\n".join( target_only ) |
---|
765 | print |
---|
766 | |
---|
767 | if both: |
---|
768 | print "files that are in both channels will be overwritten in the target channel" |
---|
769 | if source_only: |
---|
770 | print "files only in the source channel will be added to the target channel" |
---|
771 | if target_only: |
---|
772 | print "files only in the target channel will be deleted" |
---|
773 | |
---|
774 | if not (both or source_only or target_only): |
---|
775 | logging.info( "nothing to do" ) |
---|
776 | return |
---|
777 | |
---|
778 | if not self.user_confirm('perform synchronisation [y/N]:'): return |
---|
779 | |
---|
780 | source_data_list = self.client.configchannel.lookupFileInfo(\ |
---|
781 | self.session, source_channel, |
---|
782 | list( both ) + list(source_only) ) |
---|
783 | |
---|
784 | |
---|
785 | # TODO: check if this newly available function can be used instead: |
---|
786 | # self.configchannel_sync_by_backup_import() |
---|
787 | for source_data in source_data_list: |
---|
788 | if source_data.get('type') == 'file' or source_data.get('type') == 'directory': |
---|
789 | if source_data.get('contents') and not source_data.get('binary'): |
---|
790 | contents = source_data.get('contents').encode('base64') |
---|
791 | else: |
---|
792 | contents = source_data.get('contents') |
---|
793 | target_data = { |
---|
794 | 'contents': contents, |
---|
795 | 'contents_enc64': True, |
---|
796 | 'owner': source_data.get('owner'), |
---|
797 | 'group': source_data.get('group'), |
---|
798 | #'permissions': str(source_data.get('permissions')), |
---|
799 | 'permissions': source_data.get('permissions_mode'), |
---|
800 | 'selinux_ctx': source_data.get('selinux_ctx'), |
---|
801 | 'macro-start-delimiter': source_data.get('macro-start-delimiter'), |
---|
802 | 'macro-end-delimiter': source_data.get('macro-end-delimiter'), |
---|
803 | } |
---|
804 | for k,v in target_data.items(): |
---|
805 | if not v: |
---|
806 | del target_data[k] |
---|
807 | logging.debug( source_data.get('path') + ": " + str(target_data) ) |
---|
808 | self.client.configchannel.createOrUpdatePath(self.session, |
---|
809 | target_channel, |
---|
810 | source_data.get('path'), |
---|
811 | source_data.get('type') == 'directory', |
---|
812 | target_data) |
---|
813 | |
---|
814 | elif source_data.get('type') == 'symlink': |
---|
815 | target_data = { |
---|
816 | 'target_path': source_data.get('target_path'), |
---|
817 | 'selinux_ctx': source_data.get('selinux_ctx'), |
---|
818 | } |
---|
819 | logging.debug( source_data.get('path') + ": " + str(target_data) ) |
---|
820 | self.client.configchannel.createOrUpdateSymlink(self.session, |
---|
821 | target_channel, |
---|
822 | source_data.get('path'), |
---|
823 | target_data ) |
---|
824 | |
---|
825 | else: |
---|
826 | logging.warning( "unknown file type " + source_data.type ) |
---|
827 | |
---|
828 | |
---|
829 | # removing all files from target channel that did not exist on source channel |
---|
830 | if target_only: |
---|
831 | self.do_configchannel_removefiles( target_channel + " " + " ".join(target_only) ) |
---|
832 | |
---|
833 | |
---|
834 | #################### |
---|
835 | |
---|
836 | # |
---|
837 | # kickstart |
---|
838 | # |
---|
839 | |
---|
840 | # kickstart helper |
---|
841 | |
---|
842 | def is_kickstart( self, name ): |
---|
843 | if not name: return |
---|
844 | return name in self.do_kickstart_list( name, True ) |
---|
845 | |
---|
846 | def check_kickstart( self, name ): |
---|
847 | if not name: |
---|
848 | logging.error( "no kickstart label given" ) |
---|
849 | return False |
---|
850 | if not self.is_kickstart( name ): |
---|
851 | logging.error( "invalid kickstart label " + name ) |
---|
852 | return False |
---|
853 | return True |
---|
854 | |
---|
855 | |
---|
856 | # kickstart next |
---|
857 | |
---|
858 | def help_stage_kickstart_next(self): |
---|
859 | print 'stage_kickstart_next: get kickstart name for the next stage' |
---|
860 | print ' ' |
---|
861 | print 'usage: stage_kickstart_next CHANNEL' |
---|
862 | |
---|
863 | def complete_stage_kickstart_next(self, text, line, beg, end): |
---|
864 | parts = shlex.split(line) |
---|
865 | if line[-1] == ' ': parts.append('') |
---|
866 | args = len(parts) |
---|
867 | |
---|
868 | if args == 2: |
---|
869 | return tab_completer(self.do_kickstart_list('', True), text) |
---|
870 | return [] |
---|
871 | |
---|
872 | def do_stage_kickstart_next(self, args, doreturn = False): |
---|
873 | (args, options) = parse_arguments(args) |
---|
874 | |
---|
875 | if len(args) != 1: |
---|
876 | self.help_stage_kickstart_next() |
---|
877 | return |
---|
878 | |
---|
879 | source_name = args[0] |
---|
880 | if not self.is_kickstart(source_name): |
---|
881 | logging.warning( "invalid kickstart "+source_name ) |
---|
882 | return |
---|
883 | logging.debug( "source: " + str(source_name) ) |
---|
884 | target_name = self.get_next_stage_name( source_name ) |
---|
885 | logging.debug( "target: " + str(target_name) ) |
---|
886 | if not target_name: return |
---|
887 | # check target name |
---|
888 | if not self.is_kickstart(target_name): |
---|
889 | if not doreturn: |
---|
890 | logging.warning( "a next stage kickstart for "+source_name+" ("+target_name+") does not exist" ) |
---|
891 | return |
---|
892 | |
---|
893 | if doreturn: |
---|
894 | return target_name |
---|
895 | else: |
---|
896 | print target_name |
---|
897 | |
---|
898 | |
---|
899 | # kickstart diff |
---|
900 | |
---|
901 | def help_stage_kickstart_diff(self): |
---|
902 | print 'stage_kickstart_diff: diff kickstart files' |
---|
903 | print '' |
---|
904 | print 'usage: stage_kickstart_diff SOURCE_CHANNEL [TARGET_CHANNEL]' |
---|
905 | |
---|
906 | def complete_stage_kickstart_diff(self, text, line, beg, end): |
---|
907 | parts = shlex.split(line) |
---|
908 | if line[-1] == ' ': parts.append('') |
---|
909 | args = len(parts) |
---|
910 | |
---|
911 | if args == 2: |
---|
912 | return tab_completer(self.do_kickstart_list('', True), text) |
---|
913 | if args == 3: |
---|
914 | return tab_completer(self.do_kickstart_list('', True), text) |
---|
915 | return [] |
---|
916 | |
---|
917 | def do_stage_kickstart_diff(self, args, doreturn = False): |
---|
918 | options = [] |
---|
919 | |
---|
920 | (args, options) = parse_arguments(args, options) |
---|
921 | |
---|
922 | if len(args) != 1 and len(args) != 2: |
---|
923 | self.help_stage_kickstart_diff() |
---|
924 | return |
---|
925 | |
---|
926 | source_channel = args[0] |
---|
927 | if not self.check_kickstart( source_channel ): return |
---|
928 | |
---|
929 | if len(args) == 2: |
---|
930 | target_channel = args[1] |
---|
931 | else: |
---|
932 | target_channel=self.do_stage_kickstart_next( source_channel, doreturn = True) |
---|
933 | if not self.check_kickstart( target_channel ): return |
---|
934 | |
---|
935 | |
---|
936 | source_data = self.dump_kickstart( source_channel, normalize=True ) |
---|
937 | target_data = self.dump_kickstart( target_channel, normalize=True ) |
---|
938 | |
---|
939 | result=[] |
---|
940 | for line in difflib.unified_diff( source_data, target_data, source_channel, target_channel ): |
---|
941 | if doreturn: |
---|
942 | result.append(line) |
---|
943 | else: |
---|
944 | print line |
---|
945 | return result |
---|
946 | |
---|
947 | |
---|
948 | |
---|
949 | #################### |
---|
950 | |
---|
951 | # |
---|
952 | # activationkey |
---|
953 | # |
---|
954 | |
---|
955 | # activationkey helper |
---|
956 | |
---|
957 | def is_activationkey( self, name ): |
---|
958 | if not name: return |
---|
959 | return name in self.do_activationkey_list( name, True ) |
---|
960 | |
---|
961 | def check_activationkey( self, name ): |
---|
962 | if not name: |
---|
963 | logging.error( "no activationkey label given" ) |
---|
964 | return False |
---|
965 | if not self.is_activationkey( name ): |
---|
966 | logging.error( "invalid activationkey label " + name ) |
---|
967 | return False |
---|
968 | return True |
---|
969 | |
---|
970 | |
---|
971 | # activationkey next |
---|
972 | |
---|
973 | def help_stage_activationkey_next(self): |
---|
974 | print 'stage_activationkey_next: get activationkey name for the next stage' |
---|
975 | print ' ' |
---|
976 | print 'usage: stage_activationkey_next CHANNEL' |
---|
977 | |
---|
978 | def complete_stage_activationkey_next(self, text, line, beg, end): |
---|
979 | parts = shlex.split(line) |
---|
980 | if line[-1] == ' ': parts.append('') |
---|
981 | args = len(parts) |
---|
982 | |
---|
983 | if args == 2: |
---|
984 | return tab_completer(self.do_activationkey_list('', True), text) |
---|
985 | return [] |
---|
986 | |
---|
987 | def do_stage_activationkey_next(self, args, doreturn = False): |
---|
988 | (args, options) = parse_arguments(args) |
---|
989 | |
---|
990 | if len(args) != 1: |
---|
991 | self.help_stage_activationkey_next() |
---|
992 | return |
---|
993 | |
---|
994 | source_name = args[0] |
---|
995 | if not self.is_activationkey(source_name): |
---|
996 | logging.warning( "invalid activationkey "+source_name ) |
---|
997 | return |
---|
998 | logging.debug( "source: " + str(source_name) ) |
---|
999 | target_name = self.get_next_stage_name( source_name ) |
---|
1000 | logging.debug( "target: " + str(target_name) ) |
---|
1001 | if not target_name: return |
---|
1002 | # check target name |
---|
1003 | if not self.is_activationkey(target_name): |
---|
1004 | if not doreturn: |
---|
1005 | logging.warning( "a next stage activationkey for "+source_name+" ("+target_name+") does not exist" ) |
---|
1006 | return |
---|
1007 | |
---|
1008 | if doreturn: |
---|
1009 | return target_name |
---|
1010 | else: |
---|
1011 | print target_name |
---|
1012 | |
---|
1013 | |
---|
1014 | # activationkey diff |
---|
1015 | |
---|
1016 | def help_stage_activationkey_diff(self): |
---|
1017 | print 'stage_activationkeyt_diff: diff activationkeys' |
---|
1018 | print '' |
---|
1019 | print 'usage: stage_activationkey_diff SOURCE_ACTIVATIONKEY [TARGET_ACTIVATIONKEY]' |
---|
1020 | |
---|
1021 | def complete_stage_activationkey_diff(self, text, line, beg, end): |
---|
1022 | parts = shlex.split(line) |
---|
1023 | if line[-1] == ' ': parts.append('') |
---|
1024 | args = len(parts) |
---|
1025 | |
---|
1026 | |
---|
1027 | if args == 2: |
---|
1028 | return tab_completer(self.do_activationkey_list('', True), text) |
---|
1029 | if args == 3: |
---|
1030 | return tab_completer(self.do_activationkey_list('', True), text) |
---|
1031 | return [] |
---|
1032 | |
---|
1033 | def do_stage_activationkey_diff(self, args, doreturn = False): |
---|
1034 | options = [] |
---|
1035 | |
---|
1036 | (args, options) = parse_arguments(args, options) |
---|
1037 | |
---|
1038 | if len(args) != 1 and len(args) != 2: |
---|
1039 | self.help_stage_activationkey_diff() |
---|
1040 | return |
---|
1041 | |
---|
1042 | source_channel = args[0] |
---|
1043 | if not self.check_activationkey( source_channel ): return |
---|
1044 | |
---|
1045 | if len(args) == 2: |
---|
1046 | target_channel = args[1] |
---|
1047 | else: |
---|
1048 | target_channel=self.do_stage_activationkey_next( source_channel, doreturn = True) |
---|
1049 | if not self.check_activationkey( target_channel ): return |
---|
1050 | |
---|
1051 | |
---|
1052 | source_data = self.dump_activationkey( source_channel, normalize=True ) |
---|
1053 | target_data = self.dump_activationkey( target_channel, normalize=True ) |
---|
1054 | |
---|
1055 | result=[] |
---|
1056 | for line in difflib.unified_diff( source_data, target_data, source_channel, target_channel ): |
---|
1057 | if doreturn: |
---|
1058 | result.append(line) |
---|
1059 | else: |
---|
1060 | print line |
---|
1061 | return result |
---|
1062 | |
---|
1063 | |
---|
1064 | |
---|
1065 | |
---|
1066 | #################### |
---|
1067 | |
---|
1068 | # |
---|
1069 | # stage_status |
---|
1070 | # stage_*_status |
---|
1071 | # |
---|
1072 | |
---|
1073 | def help_stage_status(self): |
---|
1074 | print 'stage_status: status of a stage' |
---|
1075 | print '' |
---|
1076 | print 'usage: stage_status STAGE\n' |
---|
1077 | print 'STAGE: ' + " | ".join( _STAGES ) |
---|
1078 | |
---|
1079 | def complete_stage_status(self, text, line, beg, end): |
---|
1080 | parts = shlex.split(line) |
---|
1081 | if line[-1] == ' ': parts.append('') |
---|
1082 | args = len(parts) |
---|
1083 | |
---|
1084 | |
---|
1085 | if args == 2: |
---|
1086 | return tab_completer( _STAGES, text) |
---|
1087 | |
---|
1088 | return [] |
---|
1089 | |
---|
1090 | def do_stage_status(self, args): |
---|
1091 | (args, options) = parse_arguments(args) |
---|
1092 | |
---|
1093 | if not len(args): |
---|
1094 | self.help_stage_status() |
---|
1095 | return |
---|
1096 | |
---|
1097 | stage = args[0] |
---|
1098 | if not self.check_stage( stage ): return |
---|
1099 | self.stage = stage |
---|
1100 | |
---|
1101 | self.stage_softwarechannels_status() |
---|
1102 | self.stage_configchannels_status() |
---|
1103 | self.stage_kickstarts_status() |
---|
1104 | self.stage_activationkeys_status() |
---|
1105 | |
---|
1106 | def stage_softwarechannels_status( self ): |
---|
1107 | logging.info( "softwarechannel" ) |
---|
1108 | base_channels = self.list_base_channels() |
---|
1109 | for base_channel in base_channels: |
---|
1110 | if self.is_current_stage( base_channel ): |
---|
1111 | self.check_stage_softwarechannel_status( base_channel, indent="" ) |
---|
1112 | for child_channel in self.get_softwarechannel_childchannel( base_channel ): |
---|
1113 | self.check_stage_softwarechannel_status( child_channel, indent=" " ) |
---|
1114 | |
---|
1115 | def check_stage_softwarechannel_status( self, name, indent="" ): |
---|
1116 | status="unknown" |
---|
1117 | name_next = self.do_stage_softwarechannel_next( name, doreturn=True ) |
---|
1118 | if name_next: |
---|
1119 | if self.do_stage_softwarechannel_diff( name + " " + name_next, doreturn=True ): |
---|
1120 | status="modified" |
---|
1121 | else: |
---|
1122 | status="uptodate" |
---|
1123 | else: |
---|
1124 | status="dontexist" |
---|
1125 | print_stage_status( self, name, name_next=name_next, status=status, indent=indent ) |
---|
1126 | return status |
---|
1127 | |
---|
1128 | |
---|
1129 | def stage_configchannels_status( self ): |
---|
1130 | logging.info( "configchannel" ) |
---|
1131 | configchannels = self.do_configchannel_list('', True) |
---|
1132 | |
---|
1133 | for name in configchannels: |
---|
1134 | if self.is_current_stage( name ): |
---|
1135 | self.check_stage_configchannels_status( name ) |
---|
1136 | |
---|
1137 | def check_stage_configchannels_status( self, name, indent="" ): |
---|
1138 | status="unknown" |
---|
1139 | name_next = self.do_stage_configchannel_next( name, doreturn=True ) |
---|
1140 | if name_next: |
---|
1141 | if self.do_stage_configchannel_diff( name + " " + name_next, doreturn=True ): |
---|
1142 | status="modified" |
---|
1143 | else: |
---|
1144 | status="uptodate" |
---|
1145 | else: |
---|
1146 | status="dontexist" |
---|
1147 | print_stage_status( self, name, name_next=name_next, status=status, indent=indent ) |
---|
1148 | return status |
---|
1149 | |
---|
1150 | |
---|
1151 | |
---|
1152 | def stage_kickstarts_status( self ): |
---|
1153 | logging.info( "kickstart" ) |
---|
1154 | kickstarts = self.do_kickstart_list('', True) |
---|
1155 | |
---|
1156 | for name in kickstarts: |
---|
1157 | if self.is_current_stage( name ): |
---|
1158 | self.check_stage_kickstarts_status( name ) |
---|
1159 | |
---|
1160 | def check_stage_kickstarts_status( self, name, indent="" ): |
---|
1161 | status="unknown" |
---|
1162 | name_next = self.do_stage_kickstart_next( name, doreturn=True ) |
---|
1163 | if name_next: |
---|
1164 | if self.do_stage_kickstart_diff( name + " " + name_next, doreturn=True ): |
---|
1165 | status="modified" |
---|
1166 | else: |
---|
1167 | status="uptodate" |
---|
1168 | else: |
---|
1169 | status="dontexist" |
---|
1170 | print_stage_status( self, name, name_next=name_next, status=status, indent=indent ) |
---|
1171 | return status |
---|
1172 | |
---|
1173 | |
---|
1174 | |
---|
1175 | def stage_activationkeys_status( self ): |
---|
1176 | logging.info( "activationkey" ) |
---|
1177 | activationkeys = self.do_activationkey_list('', True) |
---|
1178 | |
---|
1179 | for name in activationkeys: |
---|
1180 | if self.is_current_stage( name ): |
---|
1181 | self.check_stage_activationkey_status( name ) |
---|
1182 | |
---|
1183 | def check_stage_activationkey_status( self, name, indent="" ): |
---|
1184 | status="unknown" |
---|
1185 | name_next = self.do_stage_activationkey_next( name, doreturn=True ) |
---|
1186 | if name_next: |
---|
1187 | if self.do_stage_activationkey_diff( name + " " + name_next, doreturn=True ): |
---|
1188 | status="modified" |
---|
1189 | else: |
---|
1190 | status="uptodate" |
---|
1191 | else: |
---|
1192 | status="dontexist" |
---|
1193 | print_stage_status( self, name, name_next=name_next, status=status, indent=indent ) |
---|
1194 | return status |
---|
1195 | |
---|
1196 | |
---|
1197 | |
---|
1198 | #################### |
---|
1199 | |
---|
1200 | # |
---|
1201 | # stage_dump |
---|
1202 | # dump_* |
---|
1203 | # |
---|
1204 | |
---|
1205 | def help_stage_dump(self): |
---|
1206 | print 'stage_dump: dump infos about a stage to files' |
---|
1207 | print '' |
---|
1208 | print 'usage: stage_dump STAGE [OUTDIR]\n' |
---|
1209 | print 'STAGE: ' + " | ".join( _STAGES ) |
---|
1210 | print 'OUTDIR defaults to ' + _DUMP_BASE_DIR |
---|
1211 | |
---|
1212 | def complete_stage_dump(self, text, line, beg, end): |
---|
1213 | parts = shlex.split(line) |
---|
1214 | if line[-1] == ' ': parts.append('') |
---|
1215 | args = len(parts) |
---|
1216 | |
---|
1217 | |
---|
1218 | if args == 2: |
---|
1219 | return tab_completer( _STAGES, text) |
---|
1220 | |
---|
1221 | return [] |
---|
1222 | |
---|
1223 | def do_stage_dump(self, args): |
---|
1224 | (args, options) = parse_arguments(args) |
---|
1225 | |
---|
1226 | if not len(args): |
---|
1227 | self.help_stage_dump() |
---|
1228 | return |
---|
1229 | |
---|
1230 | stage = args[0] |
---|
1231 | if not self.check_stage( stage ): return |
---|
1232 | self.stage = stage |
---|
1233 | |
---|
1234 | |
---|
1235 | if len(args) == 2: |
---|
1236 | outputpath_base = datetime.now().strftime(os.path.expanduser(args[1])) |
---|
1237 | else: |
---|
1238 | # make the final output path be <base>/date/channel |
---|
1239 | outputpath_base = os.path.join( _DUMP_BASE_DIR, |
---|
1240 | datetime.now().strftime("%Y-%m-%d"), |
---|
1241 | stage ) |
---|
1242 | |
---|
1243 | if not self.mkdir( outputpath_base ): return |
---|
1244 | |
---|
1245 | self.dump_softwarechannels( outputpath_base + "/softwarechannel/" ) |
---|
1246 | self.dump_configchannels( outputpath_base + "/configchannel/" ) |
---|
1247 | self.dump_kickstarts( outputpath_base + "/kickstart/" ) |
---|
1248 | self.dump_activationkeys( outputpath_base + "/activationkey/" ) |
---|
1249 | |
---|
1250 | |
---|
1251 | |
---|
1252 | |
---|
1253 | def dump_softwarechannels(self, basedir): |
---|
1254 | logging.info( "softwarechannel" ) |
---|
1255 | base_channels = self.list_base_channels() |
---|
1256 | for base_channel in base_channels: |
---|
1257 | if self.is_current_stage( base_channel ): |
---|
1258 | logging.info( " " + base_channel ) |
---|
1259 | base_channel_dir = basedir + self.get_common_name(base_channel) |
---|
1260 | if not self.mkdir( base_channel_dir ): return |
---|
1261 | |
---|
1262 | packages = self.do_softwarechannel_listallpackages( base_channel, doreturn=True ) |
---|
1263 | self.dump( base_channel_dir + '/' + self.get_common_name(base_channel), packages ) |
---|
1264 | # get all child channels and pick the channels that belongs to the base channel |
---|
1265 | for child_channel in self.get_softwarechannel_childchannel( base_channel ): |
---|
1266 | logging.info( " " + child_channel ) |
---|
1267 | packages = self.dump_softwarechannel( child_channel, onlyLastestPackages=False, normalize=True ) |
---|
1268 | self.dump( base_channel_dir + '/' + self.get_common_name(child_channel), packages ) |
---|
1269 | |
---|
1270 | def dump_softwarechannel(self, name, onlyLastestPackages=True, normalize=False): |
---|
1271 | if onlyLastestPackages: |
---|
1272 | content = self.do_softwarechannel_listallpackages( name, doreturn=True ) |
---|
1273 | else: |
---|
1274 | content = self.do_softwarechannel_listallpackages( name, doreturn=True ) |
---|
1275 | return content |
---|
1276 | |
---|
1277 | def dump_configchannel_filedetails(self, name, filename, normalize=False): |
---|
1278 | # redirect stdout to string. |
---|
1279 | # to be able to reuse the existing do_activationkey_details function |
---|
1280 | old_stdout=sys.stdout |
---|
1281 | output=StringIO() |
---|
1282 | sys.stdout=output |
---|
1283 | self.do_configchannel_filedetails( name +" "+ filename ) |
---|
1284 | sys.stdout=old_stdout |
---|
1285 | content=output.getvalue().split("\n") |
---|
1286 | output.close() |
---|
1287 | |
---|
1288 | if normalize: |
---|
1289 | stage = self.get_stage_from_name( name ) |
---|
1290 | content=self.get_normalized_text( content, stage ) |
---|
1291 | return content |
---|
1292 | |
---|
1293 | def dump_configchannel(self, name, normalize=False): |
---|
1294 | # redirect stdout to string. |
---|
1295 | # to be able to reuse the existing do_activationkey_details function |
---|
1296 | old_stdout=sys.stdout |
---|
1297 | output=StringIO() |
---|
1298 | sys.stdout=output |
---|
1299 | self.do_configchannel_details( name ) |
---|
1300 | sys.stdout=old_stdout |
---|
1301 | content=output.getvalue().split("\n") |
---|
1302 | output.close() |
---|
1303 | |
---|
1304 | for filename in self.do_configchannel_listfiles(name, True): |
---|
1305 | content = content + self.dump_configchannel_filedetails(name, filename, normalize=True) |
---|
1306 | |
---|
1307 | if normalize: |
---|
1308 | stage = self.get_stage_from_name( name ) |
---|
1309 | content=self.get_normalized_text( content, stage ) |
---|
1310 | |
---|
1311 | return content |
---|
1312 | |
---|
1313 | |
---|
1314 | def dump_configchannels(self, basedir): |
---|
1315 | logging.info( "configchannel" ) |
---|
1316 | configchannels = self.do_configchannel_list( '', doreturn = True) |
---|
1317 | |
---|
1318 | for name in configchannels: |
---|
1319 | if self.is_current_stage( name ): |
---|
1320 | logging.info( " " + name ) |
---|
1321 | dir = basedir + self.get_common_name(name) |
---|
1322 | self.do_configchannel_backup( name+" "+dir ) |
---|
1323 | |
---|
1324 | |
---|
1325 | def dump_kickstart_content(self, name, normalize=False): |
---|
1326 | content = self.client.kickstart.profile.downloadRenderedKickstart(\ |
---|
1327 | self.session, name ).split("\n") |
---|
1328 | stage = self.get_stage_from_name( name ) |
---|
1329 | if normalize: |
---|
1330 | content=self.get_normalized_text( content, stage ) |
---|
1331 | return content |
---|
1332 | |
---|
1333 | def dump_kickstart_details(self, name, normalize=False): |
---|
1334 | # redirect stdout to string. |
---|
1335 | # to be able to reuse the existing do_activationkey_details function |
---|
1336 | old_stdout=sys.stdout |
---|
1337 | output=StringIO() |
---|
1338 | sys.stdout=output |
---|
1339 | self.do_kickstart_details( name ) |
---|
1340 | sys.stdout=old_stdout |
---|
1341 | content=output.getvalue().split("\n") |
---|
1342 | output.close() |
---|
1343 | |
---|
1344 | if normalize: |
---|
1345 | stage = self.get_stage_from_name( name ) |
---|
1346 | content=self.get_normalized_text( content, stage ) |
---|
1347 | |
---|
1348 | return content |
---|
1349 | |
---|
1350 | def dump_kickstart(self, name, normalize=False): |
---|
1351 | return dump_kickstart_details(self, name, normalize=normalize) |
---|
1352 | |
---|
1353 | |
---|
1354 | def dump_kickstarts(self, basedir, doreturn = False): |
---|
1355 | logging.info( "kickstart" ) |
---|
1356 | kickstarts = self.client.kickstart.listKickstarts(self.session) |
---|
1357 | |
---|
1358 | for kickstart in kickstarts: |
---|
1359 | name = kickstart.get('name') |
---|
1360 | if self.is_current_stage( name ): |
---|
1361 | logging.info( " " + name ) |
---|
1362 | dir = basedir + self.get_common_name(name) |
---|
1363 | content = self.dump_kickstart( name, normalize=True ) |
---|
1364 | if doreturn: |
---|
1365 | return content |
---|
1366 | else: |
---|
1367 | # dump kickstart details and ks file content. |
---|
1368 | # use separate files |
---|
1369 | self.dump( dir + '/' + self.get_common_name(name), content ) |
---|
1370 | self.dump( dir + '/' + self.get_common_name(name) + ".content", dump_kickstart_content(self, name, normalize=True) ) |
---|
1371 | |
---|
1372 | |
---|
1373 | def dump_activationkey(self, name, normalize=False): |
---|
1374 | # redirect stdout to string. |
---|
1375 | # to be able to reuse the existing do_activationkey_details function |
---|
1376 | old_stdout=sys.stdout |
---|
1377 | output=StringIO() |
---|
1378 | sys.stdout=output |
---|
1379 | self.do_activationkey_details( name ) |
---|
1380 | sys.stdout=old_stdout |
---|
1381 | content=output.getvalue().split("\n") |
---|
1382 | output.close() |
---|
1383 | |
---|
1384 | if normalize: |
---|
1385 | stage = self.get_stage_from_name( name ) |
---|
1386 | content=self.get_normalized_text( content, stage ) |
---|
1387 | |
---|
1388 | return content |
---|
1389 | |
---|
1390 | |
---|
1391 | |
---|
1392 | def dump_activationkeys(self, basedir): |
---|
1393 | logging.info( "activationkey" ) |
---|
1394 | activationkeys = self.do_activationkey_list('', True) |
---|
1395 | |
---|
1396 | for name in activationkeys: |
---|
1397 | if self.is_current_stage( name ): |
---|
1398 | logging.info( " " + name ) |
---|
1399 | |
---|
1400 | content = self.dump_activationkey( name, normalize=True) |
---|
1401 | |
---|
1402 | dir = basedir + self.get_common_name(name) |
---|
1403 | self.dump( dir + '/' + self.get_common_name(name), content ) |
---|
1404 | |
---|
1405 | # vim:ts=4:expandtab: |
---|