source: people/joerg.steffens/technical/spacecmd/stage.py@ 1019

Last change on this file since 1019 was 1019, checked in by joergs, on Jun 10, 2012 at 3:14:11 PM

current staging script, before moving to official project

File size: 45.8 KB
RevLine 
[1019]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
23import shlex
24from optparse import Option
25from pprint import pprint
26import sys
27from StringIO import StringIO
28from tempfile import mkdtemp
29import difflib
30from 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
71def do_stage_help( self, args, doreturn = False ):
72 print """
73Staging:
74
75The basic principle is to have every component in multiple stages.
76The 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 """
82A 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 """
89Workflow 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
100Summary:
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
105Changes are not only adding new packages,
106but also changing configuration files in the configuration channels,
107changing kickstart settings or changing activation keys.
108
109For all these changes, spacecmd stage_* commands offers functionality
110to simplify staging.
111
112Usage:
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
165def help_stage_create_skel(self):
166 print 'stage_create_skel: create initial staging structure'
167 print '''usage: stage_create_skel [options]
168
169options:
170 -l LABEL
171 -a ARCHITECTURE ['ia32', 'x86_64']
172 -s SUB (e.g. application1)'''
173
174def 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
208def 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
291def is_stage( self, name ):
292 return name in _STAGES
293
294def 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
304def is_current_stage(self, name):
305 return "-"+self.stage in name
306
307def 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
316def get_stage_from_name( self, name ):
317 for i in _STAGES:
318 if "-"+i in name:
319 return i
320
321def get_next_stage( self, current_stage ):
322 return _STAGE_TRANSITIONS.get(current_stage)
323
324def 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
328def 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
336def 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
344def 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
351def 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
361def 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
384def is_softwarechannel( self, name ):
385 if not name: return
386 return name in self.do_softwarechannel_list( name, True )
387
388def 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
397def 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
409def 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
414def 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
423def 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
452def 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
457def 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
468def 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
503def 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
508def 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
520def 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
602def is_configchannel( self, name ):
603 if not name: return
604 return name in self.do_configchannel_list( name, True )
605
606def 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
618def 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
623def 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
632def 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
661def 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
666def 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
677def 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
709def 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
714def 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
726def 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
842def is_kickstart( self, name ):
843 if not name: return
844 return name in self.do_kickstart_list( name, True )
845
846def 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
858def 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
863def 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
872def 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
901def 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
906def 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
917def 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
957def is_activationkey( self, name ):
958 if not name: return
959 return name in self.do_activationkey_list( name, True )
960
961def 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
973def 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
978def 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
987def 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
1016def 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
1021def 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
1033def 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
1073def 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
1079def 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
1090def 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
1106def 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
1115def 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
1129def 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
1137def 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
1152def 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
1160def 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
1175def 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
1183def 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
1205def 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
1212def 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
1223def 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
1253def 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
1270def 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
1277def 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
1293def 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
1314def 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
1325def 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
1333def 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
1350def dump_kickstart(self, name, normalize=False):
1351 return dump_kickstart_details(self, name, normalize=normalize)
1352
1353
1354def 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
1373def 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
1392def 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:
Note: See TracBrowser for help on using the repository browser.