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