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 spacecmd.utils import *
|
---|
28 |
|
---|
29 | _STAGE1='dev'
|
---|
30 | _STAGE2='stg'
|
---|
31 | _STAGE3='prd'
|
---|
32 |
|
---|
33 | _STAGES=[ _STAGE1, _STAGE2, _STAGE3 ]
|
---|
34 |
|
---|
35 | _STAGE_NAMES={
|
---|
36 | 'dev': 'Development',
|
---|
37 | # alternative
|
---|
38 | 'qas': 'QualityAssurance',
|
---|
39 | 'stg': 'Staging',
|
---|
40 | 'prd': 'Production'
|
---|
41 | }
|
---|
42 | _STAGE_TRANSITIONS={
|
---|
43 | _STAGE1: _STAGE2,
|
---|
44 | _STAGE2: _STAGE3
|
---|
45 | }
|
---|
46 | _STAGE_STATUS={
|
---|
47 | "uptodate": " ",
|
---|
48 | "modified": "M",
|
---|
49 | "dontexist": "!",
|
---|
50 | "unknown": "?"
|
---|
51 | }
|
---|
52 |
|
---|
53 | _DUMP_BASE_DIR="/tmp/spacecmd-stage-dump/"
|
---|
54 |
|
---|
55 | ####################
|
---|
56 |
|
---|
57 | def do_stage_help( self, args, doreturn = False ):
|
---|
58 | print """
|
---|
59 | Staging:
|
---|
60 |
|
---|
61 | The basic principle is to have every component in multiple stages.
|
---|
62 | The stages in this environment are:"""
|
---|
63 | for stage in _STAGES:
|
---|
64 | successor=self.get_next_stage(stage)
|
---|
65 | print " " + stage + ":" , _STAGE_NAMES.get(stage)
|
---|
66 |
|
---|
67 | print """
|
---|
68 | A stage can have a successor, in our enviroment these are:"""
|
---|
69 | for stage in _STAGES:
|
---|
70 | successor=self.get_next_stage(stage)
|
---|
71 | if successor:
|
---|
72 | print " " + stage, "->" , successor
|
---|
73 |
|
---|
74 | print """
|
---|
75 | Workflow example:
|
---|
76 | * creating a new package/package version
|
---|
77 | the new package is added to a {stage1} softwarechannel.
|
---|
78 | If the package seams to work correctly,
|
---|
79 | a new integration phase can be started.
|
---|
80 | For this, the packages are copied from {stage1} to {stage2}.
|
---|
81 | The {stage2} stage is then tested.
|
---|
82 | After a successful test, all content of {stage2} is transfered to {stage3}.
|
---|
83 | When the content has arrived in {stage3},
|
---|
84 | all productively used systems are able to update to the new content.
|
---|
85 |
|
---|
86 | Summary:
|
---|
87 | {stage1}: all changes are done to {stage1}
|
---|
88 | {stage2}: integration tests are done in {stage2} only
|
---|
89 | {stage3}: productively used systems using {stage3} only
|
---|
90 |
|
---|
91 | Changes are not only adding new packages,
|
---|
92 | but also changing configuration files in the configuration channels,
|
---|
93 | changing kickstart settings or changing activation keys.
|
---|
94 |
|
---|
95 | For all these changes, spacecmd stage_* commands offers functionality
|
---|
96 | to simplify staging.
|
---|
97 |
|
---|
98 | Usage:
|
---|
99 | * create your channels, actionvationkey and so on.
|
---|
100 | Because Spacewalk does not know about staging directly,
|
---|
101 | staging information must be coded into the name of the components.
|
---|
102 | The name must include the stage, separeted by '-',
|
---|
103 | eg. centos6-x86_64-{stage1}, centos6-x86_64-{stage1}-subchannel, ks-centos6-x86_64-{stage1}-common, ...
|
---|
104 | To create a initial structure, the comamnd 'stage_create_skel' can be used.
|
---|
105 |
|
---|
106 | * check the staging status by 'stage_status STAGE'
|
---|
107 | This will select all components from stage 'STAGE' and compare each component with the correcponding component from the successor stage, eg.:
|
---|
108 | 'stage_status {stage1}'
|
---|
109 | INFO: softwarechannel
|
---|
110 | centos6-x86_64-{stage1} -> centos6-x86_64-{stage2}
|
---|
111 | M centos6-x86_64-{stage1}-app1 -> centos6-x86_64-{stage2}-app1
|
---|
112 | ! centos6-x86_64-{stage1}-app2
|
---|
113 | INFO: configchannel
|
---|
114 | cfg-centos6-x86_64-{stage1}-app1 -> cfg-centos6-x86_64-{stage2}-app1
|
---|
115 | INFO: kickstart
|
---|
116 | M ks-centos6-x86_64-{stage1}-app1 -> ks-centos6-x86_64-{stage2}-app1
|
---|
117 | INFO: activationkey
|
---|
118 | 1-centos6-x86_64-{stage1}-app1 -> 1-centos6-x86_64-{stage2}-app1
|
---|
119 |
|
---|
120 | This first column indicates the state:
|
---|
121 | : empty: no differences. The components from both stages are indentical
|
---|
122 | ! : no correcponding component in successor stage found
|
---|
123 | M : modification. The component differs between the current and the successor stage
|
---|
124 |
|
---|
125 | * The most interessting entries are the modified entires.
|
---|
126 | To check this more specifically, use the corresponding 'stage_*_diff' function, eg.
|
---|
127 | 'stage_softwarechannel_diff centos7-x86_64-{stage1}-app1'
|
---|
128 | --- centos6-x86_64-{stage1}-app1
|
---|
129 |
|
---|
130 | +++ centos6-x86_64-{stage2}-app1
|
---|
131 |
|
---|
132 | @@ -1,1 +1,0 @@
|
---|
133 |
|
---|
134 | -newpackage-1.0.1-1.1.noarch
|
---|
135 |
|
---|
136 | (it is also possible to compare two specific subchannel, eg.
|
---|
137 | 'stage_softwarechannel_diff centos6-x86_64-{stage1}-subchannel1 centos6-x86_64-{stage2}-subchannel1'
|
---|
138 | but the corresponding successor stage component is found automatically by its name)
|
---|
139 |
|
---|
140 | * Softwarechannel and configchannel also offers the stage_*_sync function.
|
---|
141 | Use them, to copy the content of a component to the next stage, e.g.
|
---|
142 | 'stage_softwarechannel_sync centos6-x86_64-{stage1}-app1'
|
---|
143 | INFO: syncing packages from softwarechannel centos6-x86_64-{stage1}-app1 to centos6-x86_64-{stage2}-app1
|
---|
144 | packages to add to channel "centos6-x86_64-{stage2}-app1":
|
---|
145 | newpackage-1.0.1-1.1.noarch
|
---|
146 | Perform these changes to channel centos6-x86_64-{stage2}-app1 [y/N]:
|
---|
147 |
|
---|
148 | * Repeat these steps, until 'stage_status STAGE' shows no differences between the two stages
|
---|
149 | """.format(stage1=_STAGE1, stage2=_STAGE2, stage3=_STAGE3)
|
---|
150 |
|
---|
151 | def help_stage_create_skel(self):
|
---|
152 | print 'stage_create_skel: create initial staging structure'
|
---|
153 | print '''usage: stage_create_skel [options]
|
---|
154 |
|
---|
155 | options:
|
---|
156 | -l LABEL
|
---|
157 | -a ARCHITECTURE ['ia32', 'x86_64']
|
---|
158 | -s SUB (e.g. application1)'''
|
---|
159 |
|
---|
160 | def do_stage_create_skel(self, args):
|
---|
161 | options = [
|
---|
162 | Option('-l', '--label', action='store'),
|
---|
163 | Option('-a', '--arch', action='store'),
|
---|
164 | Option('-s', '--sub', action='store'),
|
---|
165 | ]
|
---|
166 |
|
---|
167 | (args, options) = parse_arguments(args, options)
|
---|
168 |
|
---|
169 | if is_interactive(options):
|
---|
170 | options.label = prompt_user('Channel Label:', noblank = True)
|
---|
171 |
|
---|
172 | print
|
---|
173 | print 'Architecture'
|
---|
174 | print '------------'
|
---|
175 | print '\n'.join(sorted(self.ARCH_LABELS))
|
---|
176 | print
|
---|
177 | options.arch = prompt_user('Select:')
|
---|
178 | options.arch = prompt_user('Sub:')
|
---|
179 | else:
|
---|
180 | if not options.label:
|
---|
181 | logging.error('A channel label is required')
|
---|
182 | return
|
---|
183 |
|
---|
184 | if not options.arch:
|
---|
185 | logging.error('An architecture is required')
|
---|
186 | return
|
---|
187 |
|
---|
188 | if self.stage_create_skel( options.label, options.arch, options.sub, create=False ):
|
---|
189 | self.stage_create_skel( options.label, options.arch, options.sub, create=True )
|
---|
190 |
|
---|
191 |
|
---|
192 | def stage_create_skel(self, dist, arch, sub, create = False):
|
---|
193 |
|
---|
194 | org = "1"
|
---|
195 | disttype = "rhel_6"
|
---|
196 | application = sub
|
---|
197 |
|
---|
198 | print
|
---|
199 | for stage in _STAGES:
|
---|
200 | base = dist + "-" + arch + "-" + stage
|
---|
201 | softwarechannel_base = base
|
---|
202 | softwarechannel_sub = base + "-" + application
|
---|
203 | distribution = "dist-" + base
|
---|
204 | distributionpath = "/srv/dist/" + base
|
---|
205 | configchannel = "cfg-" + base + "-" + application
|
---|
206 | activationkey_create = base + "-" + application
|
---|
207 | activationkey = org + "-" + activationkey_create
|
---|
208 | kickstart = "ks-" + base + "-" + application
|
---|
209 |
|
---|
210 | print "stage: " + stage
|
---|
211 |
|
---|
212 | print "softwarechannel base: " + softwarechannel_base,
|
---|
213 | if self.is_softwarechannel( softwarechannel_base ):
|
---|
214 | print " [exists]",
|
---|
215 | elif create:
|
---|
216 | self.do_softwarechannel_create( "-n " + softwarechannel_base + " -l " + softwarechannel_base + " -a " + arch )
|
---|
217 | print
|
---|
218 |
|
---|
219 | print "softwarechannel subchannel: " + softwarechannel_sub,
|
---|
220 | if self.is_softwarechannel( softwarechannel_sub ):
|
---|
221 | print " [exists]",
|
---|
222 | elif create:
|
---|
223 | self.do_softwarechannel_create( "-n " + softwarechannel_sub + " -l " + softwarechannel_sub + " -a " + arch + " -p " + base )
|
---|
224 | print
|
---|
225 |
|
---|
226 |
|
---|
227 | print "distribution: " + distribution + " (distribution path: " + distributionpath + ")",
|
---|
228 | if distribution in self.do_distribution_list(distribution, True):
|
---|
229 | print " [exists]",
|
---|
230 | elif create:
|
---|
231 | self.do_distribution_create( "--name " + distribution + " --path " + distributionpath + " --base-channel " + base + " --install-type " + disttype )
|
---|
232 | print
|
---|
233 |
|
---|
234 | print "configchannel: " + configchannel,
|
---|
235 | if self.is_configchannel( configchannel ):
|
---|
236 | print " [exists]",
|
---|
237 | elif create:
|
---|
238 | self.do_configchannel_create( "-n " + configchannel )
|
---|
239 | print
|
---|
240 |
|
---|
241 | print "activationkey: " + activationkey,
|
---|
242 | if self.is_activationkey( activationkey ):
|
---|
243 | print " [exists]",
|
---|
244 | elif create:
|
---|
245 | self.do_activationkey_create( "-n " + activationkey_create + " -d " + activationkey + " -b " + base + " -e provisioning_entitled" )
|
---|
246 | self.do_activationkey_addchildchannels( activationkey + " " + softwarechannel_sub )
|
---|
247 | self.do_activationkey_enableconfigdeployment( activationkey )
|
---|
248 | self.do_activationkey_addconfigchannels( activationkey + " " + configchannel + " -t" )
|
---|
249 | print
|
---|
250 |
|
---|
251 | print "kickstart: " + kickstart,
|
---|
252 | if self.is_kickstart( kickstart ):
|
---|
253 | print " [exists]",
|
---|
254 | elif create:
|
---|
255 | self.do_kickstart_create( "--name=" + kickstart + " --distribution=" + distribution + " --root-password=CHANGEME --virt-type=none" )
|
---|
256 | self.do_kickstart_addactivationkeys( kickstart + " " + activationkey )
|
---|
257 | self.do_kickstart_enableconfigmanagement( kickstart )
|
---|
258 | self.do_kickstart_enablelogging( kickstart )
|
---|
259 | print
|
---|
260 |
|
---|
261 | print
|
---|
262 |
|
---|
263 | if not create:
|
---|
264 | print "Make sure, distribution trees are available at the specified distribution paths."
|
---|
265 | return self.user_confirm('Create this components [y/N]:')
|
---|
266 |
|
---|
267 |
|
---|
268 |
|
---|
269 | ####################
|
---|
270 |
|
---|
271 | #
|
---|
272 | # helper functions
|
---|
273 | #
|
---|
274 |
|
---|
275 | def is_stage( self, name ):
|
---|
276 | return name in _STAGES
|
---|
277 |
|
---|
278 | def check_stage( self, name ):
|
---|
279 | """Checks if name describes a vaild stage"""
|
---|
280 | if not name:
|
---|
281 | logging.error( "no stage given" )
|
---|
282 | return False
|
---|
283 | if not self.is_stage( name ):
|
---|
284 | logging.error( "invalid stage " + name )
|
---|
285 | return False
|
---|
286 | return True
|
---|
287 |
|
---|
288 | def is_current_stage(self, name):
|
---|
289 | return "-"+self.stage in name
|
---|
290 |
|
---|
291 | def get_common_name( self, name ):
|
---|
292 | """Returns the name with the stage replaced by 'STAGE'
|
---|
293 |
|
---|
294 | To check the differences from 2 components that are in different stages,
|
---|
295 | the specific stage is replaced by the word 'STAGE'
|
---|
296 | """
|
---|
297 | return self.replace_stage_in_name( name, self.stage, "STAGE" )
|
---|
298 |
|
---|
299 |
|
---|
300 | def get_stage_from_name( self, name ):
|
---|
301 | for i in _STAGES:
|
---|
302 | if "-"+i in name:
|
---|
303 | return i
|
---|
304 |
|
---|
305 | def get_next_stage( self, current_stage ):
|
---|
306 | return _STAGE_TRANSITIONS.get(current_stage)
|
---|
307 |
|
---|
308 | def replace_stage_in_name( self, name, current_stage, new_stage ):
|
---|
309 | """Return the name with current stage replaced by new stage"""
|
---|
310 | return name.replace( "-"+current_stage, "-"+new_stage )
|
---|
311 |
|
---|
312 | def get_next_stage_name( self, name ):
|
---|
313 | current_stage = self.get_stage_from_name( name )
|
---|
314 | if not current_stage:
|
---|
315 | return
|
---|
316 | next_stage = self.get_next_stage( current_stage )
|
---|
317 | if not next_stage:
|
---|
318 | return
|
---|
319 | next_stage_name = self.replace_stage_in_name( name, current_stage, next_stage )
|
---|
320 | return next_stage_name
|
---|
321 |
|
---|
322 | def print_stage_status( self, name, name_next=None, status="unknown", indent="" ):
|
---|
323 | width=48-len(indent)
|
---|
324 | string = '{status_code} {indent}{name:{width}}'.format(status_code=_STAGE_STATUS.get(status), indent=indent, name=name, width=width )
|
---|
325 | if name_next:
|
---|
326 | string = string + " -> " + indent + name_next
|
---|
327 | print string
|
---|
328 |
|
---|
329 | def mkdir(self, name ):
|
---|
330 | try:
|
---|
331 | if not os.path.isdir( name ):
|
---|
332 | os.makedirs( name )
|
---|
333 | logging.debug( "creating directory " + name )
|
---|
334 | return True
|
---|
335 | except:
|
---|
336 | logging.error('Failed to create directory ' + name )
|
---|
337 | return False
|
---|
338 |
|
---|
339 | def dump(self, filename, data, raw=False):
|
---|
340 | """Writes data to filename"""
|
---|
341 | if not self.mkdir( os.path.dirname( filename )): return False
|
---|
342 | try:
|
---|
343 | fh = open( filename, 'w' )
|
---|
344 | if( raw ):
|
---|
345 | fh.write(data)
|
---|
346 | else:
|
---|
347 | fh.write("\n".join(data))
|
---|
348 | fh.close()
|
---|
349 | except:
|
---|
350 | logging.error('failed to create file ' + filename )
|
---|
351 | return False
|
---|
352 |
|
---|
353 |
|
---|
354 | ####################
|
---|
355 |
|
---|
356 | #
|
---|
357 | # softwarechannel
|
---|
358 | #
|
---|
359 |
|
---|
360 | def get_softwarechannel_childchannel( self, base_channel ):
|
---|
361 | result=[]
|
---|
362 | for child_channel in self.list_child_channels():
|
---|
363 | details = self.client.channel.software.getDetails(\
|
---|
364 | self.session, child_channel)
|
---|
365 | if details.get('parent_channel_label') == base_channel:
|
---|
366 | result.append( child_channel )
|
---|
367 | return result
|
---|
368 |
|
---|
369 |
|
---|
370 |
|
---|
371 | # softwarechannel next
|
---|
372 |
|
---|
373 | def help_stage_softwarechannel_next(self):
|
---|
374 | print 'stage_softwarechannel_next: get softwarechannel name for the next stage'
|
---|
375 | print ' '
|
---|
376 | print 'usage: stage_softwarechannel_next CHANNEL'
|
---|
377 |
|
---|
378 | def complete_stage_softwarechannel_next(self, text, line, beg, end):
|
---|
379 | parts = shlex.split(line)
|
---|
380 | if line[-1] == ' ': parts.append('')
|
---|
381 | args = len(parts)
|
---|
382 |
|
---|
383 | if args == 2:
|
---|
384 | return tab_completer(self.do_softwarechannel_list('', True), text)
|
---|
385 | return []
|
---|
386 |
|
---|
387 | def do_stage_softwarechannel_next(self, args):
|
---|
388 | (args, options) = parse_arguments(args)
|
---|
389 |
|
---|
390 | if len(args) != 1:
|
---|
391 | self.help_stage_softwarechannel_next()
|
---|
392 | return
|
---|
393 |
|
---|
394 | source_name = args[0]
|
---|
395 | if not self.is_softwarechannel(source_name):
|
---|
396 | logging.warning( "invalid softwarechannel "+source_name )
|
---|
397 | return
|
---|
398 | logging.debug( "source: " + str(source_name) )
|
---|
399 | target_name = self.get_next_stage_name( source_name )
|
---|
400 | logging.debug( "target: " + str(target_name) )
|
---|
401 | if not target_name: return
|
---|
402 | # check target name
|
---|
403 | if not self.is_softwarechannel(target_name):
|
---|
404 | logging.debug( "a next stage softwarechannel for "+source_name+" ("+target_name+") does not exist" )
|
---|
405 | return
|
---|
406 |
|
---|
407 | return target_name
|
---|
408 |
|
---|
409 |
|
---|
410 |
|
---|
411 | ####################
|
---|
412 |
|
---|
413 | #
|
---|
414 | # configchannel
|
---|
415 | #
|
---|
416 |
|
---|
417 | # configchannel next
|
---|
418 |
|
---|
419 | def help_stage_configchannel_next(self):
|
---|
420 | print 'stage_configchannel_next: get configchannel name for the next stage'
|
---|
421 | print ' '
|
---|
422 | print 'usage: stage_configchannel_next CHANNEL'
|
---|
423 |
|
---|
424 | def complete_stage_configchannel_next(self, text, line, beg, end):
|
---|
425 | parts = shlex.split(line)
|
---|
426 | if line[-1] == ' ': parts.append('')
|
---|
427 | args = len(parts)
|
---|
428 |
|
---|
429 | if args == 2:
|
---|
430 | return tab_completer(self.do_configchannel_list('', True), text)
|
---|
431 | return []
|
---|
432 |
|
---|
433 | def do_stage_configchannel_next(self, args):
|
---|
434 | (args, options) = parse_arguments(args)
|
---|
435 |
|
---|
436 | if len(args) != 1:
|
---|
437 | self.help_stage_configchannel_next()
|
---|
438 | return
|
---|
439 |
|
---|
440 | source_name = args[0]
|
---|
441 | if not self.is_configchannel(source_name):
|
---|
442 | logging.warning( "invalid configchannel "+source_name )
|
---|
443 | return
|
---|
444 | logging.debug( "source: " + str(source_name) )
|
---|
445 | target_name = self.get_next_stage_name( source_name )
|
---|
446 | logging.debug( "target: " + str(target_name) )
|
---|
447 | if not target_name: return
|
---|
448 | # check target name
|
---|
449 | if not self.is_configchannel(target_name):
|
---|
450 | logging.debug( "a next stage configchannel for "+source_name+" ("+target_name+") does not exist" )
|
---|
451 | return
|
---|
452 | return target_name
|
---|
453 |
|
---|
454 |
|
---|
455 | ####################
|
---|
456 |
|
---|
457 | #
|
---|
458 | # kickstart
|
---|
459 | #
|
---|
460 |
|
---|
461 | # kickstart next
|
---|
462 |
|
---|
463 | def help_stage_kickstart_next(self):
|
---|
464 | print 'stage_kickstart_next: get kickstart name for the next stage'
|
---|
465 | print ' '
|
---|
466 | print 'usage: stage_kickstart_next CHANNEL'
|
---|
467 |
|
---|
468 | def complete_stage_kickstart_next(self, text, line, beg, end):
|
---|
469 | parts = shlex.split(line)
|
---|
470 | if line[-1] == ' ': parts.append('')
|
---|
471 | args = len(parts)
|
---|
472 |
|
---|
473 | if args == 2:
|
---|
474 | return tab_completer(self.do_kickstart_list('', True), text)
|
---|
475 | return []
|
---|
476 |
|
---|
477 | def do_stage_kickstart_next(self, args):
|
---|
478 | (args, options) = parse_arguments(args)
|
---|
479 |
|
---|
480 | if len(args) != 1:
|
---|
481 | self.help_stage_kickstart_next()
|
---|
482 | return
|
---|
483 |
|
---|
484 | source_name = args[0]
|
---|
485 | if not self.is_kickstart(source_name):
|
---|
486 | logging.warning( "invalid kickstart "+source_name )
|
---|
487 | return
|
---|
488 | logging.debug( "source: " + str(source_name) )
|
---|
489 | target_name = self.get_next_stage_name( source_name )
|
---|
490 | logging.debug( "target: " + str(target_name) )
|
---|
491 | if not target_name: return
|
---|
492 | # check target name
|
---|
493 | if not self.is_kickstart(target_name):
|
---|
494 | logging.debug( "a next stage kickstart for "+source_name+" ("+target_name+") does not exist" )
|
---|
495 | return
|
---|
496 | return target_name
|
---|
497 |
|
---|
498 | ####################
|
---|
499 |
|
---|
500 | #
|
---|
501 | # activationkey
|
---|
502 | #
|
---|
503 |
|
---|
504 | # activationkey next
|
---|
505 |
|
---|
506 | def help_stage_activationkey_next(self):
|
---|
507 | print 'stage_activationkey_next: get activationkey name for the next stage'
|
---|
508 | print ' '
|
---|
509 | print 'usage: stage_activationkey_next CHANNEL'
|
---|
510 |
|
---|
511 | def complete_stage_activationkey_next(self, text, line, beg, end):
|
---|
512 | parts = shlex.split(line)
|
---|
513 | if line[-1] == ' ': parts.append('')
|
---|
514 | args = len(parts)
|
---|
515 |
|
---|
516 | if args == 2:
|
---|
517 | return tab_completer(self.do_activationkey_list('', True), text)
|
---|
518 | return []
|
---|
519 |
|
---|
520 | def do_stage_activationkey_next(self, args):
|
---|
521 | (args, options) = parse_arguments(args)
|
---|
522 |
|
---|
523 | if len(args) != 1:
|
---|
524 | self.help_stage_activationkey_next()
|
---|
525 | return
|
---|
526 |
|
---|
527 | source_name = args[0]
|
---|
528 | if not self.is_activationkey(source_name):
|
---|
529 | logging.warning( "invalid activationkey "+source_name )
|
---|
530 | return
|
---|
531 | logging.debug( "source: " + str(source_name) )
|
---|
532 | target_name = self.get_next_stage_name( source_name )
|
---|
533 | logging.debug( "target: " + str(target_name) )
|
---|
534 | if not target_name: return
|
---|
535 | # check target name
|
---|
536 | if not self.is_activationkey(target_name):
|
---|
537 | logging.debug( "a next stage activationkey for "+source_name+" ("+target_name+") does not exist" )
|
---|
538 | return
|
---|
539 | return target_name
|
---|
540 |
|
---|
541 |
|
---|
542 | ####################
|
---|
543 |
|
---|
544 | #
|
---|
545 | # stage_status
|
---|
546 | # stage_*_status
|
---|
547 | #
|
---|
548 |
|
---|
549 | def help_stage_status(self):
|
---|
550 | print 'stage_status: status of a stage'
|
---|
551 | print ''
|
---|
552 | print 'usage: stage_status STAGE\n'
|
---|
553 | print 'STAGE: ' + " | ".join( _STAGES )
|
---|
554 |
|
---|
555 | def complete_stage_status(self, text, line, beg, end):
|
---|
556 | parts = shlex.split(line)
|
---|
557 | if line[-1] == ' ': parts.append('')
|
---|
558 | args = len(parts)
|
---|
559 |
|
---|
560 | if args == 2:
|
---|
561 | return tab_completer( _STAGES, text)
|
---|
562 |
|
---|
563 | return []
|
---|
564 |
|
---|
565 | def do_stage_status(self, args):
|
---|
566 | (args, options) = parse_arguments(args)
|
---|
567 |
|
---|
568 | if not len(args):
|
---|
569 | self.help_stage_status()
|
---|
570 | return
|
---|
571 |
|
---|
572 | stage = args[0]
|
---|
573 | if not self.check_stage( stage ): return
|
---|
574 | self.stage = stage
|
---|
575 |
|
---|
576 | self.stage_softwarechannels_status()
|
---|
577 | self.stage_configchannels_status()
|
---|
578 | self.stage_kickstarts_status()
|
---|
579 | self.stage_activationkeys_status()
|
---|
580 |
|
---|
581 | def stage_softwarechannels_status( self ):
|
---|
582 | logging.info( "softwarechannel" )
|
---|
583 | base_channels = self.list_base_channels()
|
---|
584 | for base_channel in base_channels:
|
---|
585 | if self.is_current_stage( base_channel ):
|
---|
586 | self.check_stage_softwarechannel_status( base_channel, indent="" )
|
---|
587 | for child_channel in self.get_softwarechannel_childchannel( base_channel ):
|
---|
588 | self.check_stage_softwarechannel_status( child_channel, indent=" " )
|
---|
589 |
|
---|
590 | def check_stage_softwarechannel_status( self, name, indent="" ):
|
---|
591 | status="unknown"
|
---|
592 | name_next = self.do_stage_softwarechannel_next( name )
|
---|
593 | if name_next:
|
---|
594 | if self.do_softwarechannel_diff( name + " " + name_next ):
|
---|
595 | status="modified"
|
---|
596 | else:
|
---|
597 | status="uptodate"
|
---|
598 | else:
|
---|
599 | status="dontexist"
|
---|
600 | print_stage_status( self, name, name_next=name_next, status=status, indent=indent )
|
---|
601 | return status
|
---|
602 |
|
---|
603 |
|
---|
604 | def stage_configchannels_status( self ):
|
---|
605 | logging.info( "configchannel" )
|
---|
606 | configchannels = self.do_configchannel_list('', True)
|
---|
607 |
|
---|
608 | for name in configchannels:
|
---|
609 | if self.is_current_stage( name ):
|
---|
610 | self.check_stage_configchannels_status( name )
|
---|
611 |
|
---|
612 | def check_stage_configchannels_status( self, name, indent="" ):
|
---|
613 | status="unknown"
|
---|
614 | name_next = self.do_stage_configchannel_next( name )
|
---|
615 | if name_next:
|
---|
616 | if self.do_configchannel_diff( name + " " + name_next ):
|
---|
617 | status="modified"
|
---|
618 | else:
|
---|
619 | status="uptodate"
|
---|
620 | else:
|
---|
621 | status="dontexist"
|
---|
622 | print_stage_status( self, name, name_next=name_next, status=status, indent=indent )
|
---|
623 | return status
|
---|
624 |
|
---|
625 |
|
---|
626 |
|
---|
627 | def stage_kickstarts_status( self ):
|
---|
628 | logging.info( "kickstart" )
|
---|
629 | kickstarts = self.do_kickstart_list('', True)
|
---|
630 |
|
---|
631 | for name in kickstarts:
|
---|
632 | if self.is_current_stage( name ):
|
---|
633 | self.check_stage_kickstarts_status( name )
|
---|
634 |
|
---|
635 | def check_stage_kickstarts_status( self, name, indent="" ):
|
---|
636 | status="unknown"
|
---|
637 | name_next = self.do_stage_kickstart_next( name )
|
---|
638 | if name_next:
|
---|
639 | if self.do_kickstart_diff( name + " " + name_next ):
|
---|
640 | status="modified"
|
---|
641 | else:
|
---|
642 | status="uptodate"
|
---|
643 | else:
|
---|
644 | status="dontexist"
|
---|
645 | print_stage_status( self, name, name_next=name_next, status=status, indent=indent )
|
---|
646 | return status
|
---|
647 |
|
---|
648 |
|
---|
649 |
|
---|
650 | def stage_activationkeys_status( self ):
|
---|
651 | logging.info( "activationkey" )
|
---|
652 | activationkeys = self.do_activationkey_list('', True)
|
---|
653 |
|
---|
654 | for name in activationkeys:
|
---|
655 | if self.is_current_stage( name ):
|
---|
656 | self.check_stage_activationkey_status( name )
|
---|
657 |
|
---|
658 | def check_stage_activationkey_status( self, name, indent="" ):
|
---|
659 | status="unknown"
|
---|
660 | name_next = self.do_stage_activationkey_next( name )
|
---|
661 | if name_next:
|
---|
662 | if self.do_activationkey_diff( name + " " + name_next ):
|
---|
663 | status="modified"
|
---|
664 | else:
|
---|
665 | status="uptodate"
|
---|
666 | else:
|
---|
667 | status="dontexist"
|
---|
668 | print_stage_status( self, name, name_next=name_next, status=status, indent=indent )
|
---|
669 | return status
|
---|
670 |
|
---|
671 |
|
---|
672 |
|
---|
673 | ####################
|
---|
674 |
|
---|
675 | #
|
---|
676 | # stage_dump
|
---|
677 | # dump_*
|
---|
678 | #
|
---|
679 |
|
---|
680 | def help_stage_dump(self):
|
---|
681 | print 'stage_dump: dump infos about a stage to files'
|
---|
682 | print ''
|
---|
683 | print 'usage: stage_dump STAGE [OUTDIR]\n'
|
---|
684 | print 'STAGE: ' + " | ".join( _STAGES )
|
---|
685 | print 'OUTDIR defaults to ' + _DUMP_BASE_DIR
|
---|
686 |
|
---|
687 | def complete_stage_dump(self, text, line, beg, end):
|
---|
688 | parts = shlex.split(line)
|
---|
689 | if line[-1] == ' ': parts.append('')
|
---|
690 | args = len(parts)
|
---|
691 |
|
---|
692 |
|
---|
693 | if args == 2:
|
---|
694 | return tab_completer( _STAGES, text)
|
---|
695 |
|
---|
696 | return []
|
---|
697 |
|
---|
698 | def do_stage_dump(self, args):
|
---|
699 | (args, options) = parse_arguments(args)
|
---|
700 |
|
---|
701 | if not len(args):
|
---|
702 | self.help_stage_dump()
|
---|
703 | return
|
---|
704 |
|
---|
705 | stage = args[0]
|
---|
706 | if not self.check_stage( stage ): return
|
---|
707 | self.stage = stage
|
---|
708 |
|
---|
709 | if len(args) == 2:
|
---|
710 | outputpath_base = datetime.now().strftime(os.path.expanduser(args[1]))
|
---|
711 | else:
|
---|
712 | # make the final output path be <base>/date/channel
|
---|
713 | outputpath_base = os.path.join( _DUMP_BASE_DIR,
|
---|
714 | datetime.now().strftime("%Y-%m-%d"),
|
---|
715 | stage )
|
---|
716 |
|
---|
717 | if not self.mkdir( outputpath_base ): return
|
---|
718 |
|
---|
719 | self.dump_softwarechannels( outputpath_base + "/softwarechannel/" )
|
---|
720 | self.dump_configchannels( outputpath_base + "/configchannel/" )
|
---|
721 | self.dump_kickstarts( outputpath_base + "/kickstart/" )
|
---|
722 | self.dump_activationkeys( outputpath_base + "/activationkey/" )
|
---|
723 |
|
---|
724 |
|
---|
725 |
|
---|
726 | def dump_softwarechannels(self, basedir):
|
---|
727 | logging.info( "softwarechannel" )
|
---|
728 | base_channels = self.list_base_channels()
|
---|
729 | for base_channel in base_channels:
|
---|
730 | if self.is_current_stage( base_channel ):
|
---|
731 | logging.info( " " + base_channel )
|
---|
732 | base_channel_dir = basedir + self.get_common_name(base_channel)
|
---|
733 | if not self.mkdir( base_channel_dir ): return
|
---|
734 |
|
---|
735 | packages = self.do_softwarechannel_listallpackages( base_channel, doreturn=True )
|
---|
736 | self.dump( base_channel_dir + '/' + self.get_common_name(base_channel), packages )
|
---|
737 | # get all child channels and pick the channels that belongs to the base channel
|
---|
738 | for child_channel in self.get_softwarechannel_childchannel( base_channel ):
|
---|
739 | logging.info( " " + child_channel )
|
---|
740 | packages = self.dump_softwarechannel( child_channel )
|
---|
741 | self.dump( base_channel_dir + '/' + self.get_common_name(child_channel), packages )
|
---|
742 |
|
---|
743 |
|
---|
744 |
|
---|
745 | def dump_configchannels(self, basedir):
|
---|
746 | logging.info( "configchannel" )
|
---|
747 | configchannels = self.do_configchannel_list( '', doreturn = True)
|
---|
748 |
|
---|
749 | for name in configchannels:
|
---|
750 | if self.is_current_stage( name ):
|
---|
751 | logging.info( " " + name )
|
---|
752 | directory = basedir + self.get_common_name(name)
|
---|
753 | self.do_configchannel_backup( name+" "+directory )
|
---|
754 |
|
---|
755 |
|
---|
756 | def dump_kickstarts(self, basedir):
|
---|
757 | logging.info( "kickstart" )
|
---|
758 | kickstarts = self.client.kickstart.listKickstarts(self.session)
|
---|
759 |
|
---|
760 | for kickstart in kickstarts:
|
---|
761 | name = kickstart.get('name')
|
---|
762 | if self.is_current_stage( name ):
|
---|
763 | logging.info( " " + name )
|
---|
764 | dir = basedir + self.get_common_name(name)
|
---|
765 | content = self.dump_kickstart( name )
|
---|
766 | # dump kickstart details and ks file content.
|
---|
767 | # use separate files
|
---|
768 | self.dump( dir + '/' + self.get_common_name(name), content )
|
---|
769 | #self.dump( dir + '/' + self.get_common_name(name) + ".content", dump_kickstart_content(self, name) )
|
---|
770 |
|
---|
771 |
|
---|
772 | def dump_activationkeys(self, basedir):
|
---|
773 | logging.info( "activationkey" )
|
---|
774 | activationkeys = self.do_activationkey_list('', True)
|
---|
775 |
|
---|
776 | for name in activationkeys:
|
---|
777 | if self.is_current_stage( name ):
|
---|
778 | logging.info( " " + name )
|
---|
779 |
|
---|
780 | content = self.dump_activationkey( name )
|
---|
781 |
|
---|
782 | dir = basedir + self.get_common_name(name)
|
---|
783 | self.dump( dir + '/' + self.get_common_name(name), content )
|
---|
784 |
|
---|
785 |
|
---|
786 | # vim:ts=4:expandtab:
|
---|