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