-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Hello,

I've created some extensions for spacecmd to provide a way to handle
different stages like
- - development
- - staging
- - production

This is done by following functions:
def do_stage_create_skel
def do_stage_softwarechannel_diff
def do_stage_softwarechannel_sync
def do_stage_configchannel_diff
def do_stage_configchannel_sync
def do_stage_kickstart_diff
def do_stage_activationkey_diff
def do_stage_status
def do_stage_dump

As my initial approach
(https://bugzilla.redhat.com/show_bug.cgi?id=781883) did not make it
into spacecmd,
so now I'm now trying to add parts of the functionality as smaller
patches.

First, I've a question about ret
Most of my changes are none intrusive, however there is a behavior of
spacecmd I'd like to change:
currently, the do-functions normally have no return values at all.
Because some of these functions are used by other function, a lot of
them have an additional parameter "doreturn = False". If this is set to
true, the result is not printed to stdout, but instead return as list of
strings.

As a results, some code inside the do-functions is duplicated.
Because the new functionality I've created uses some functions that
currently do not provide a way to return there results, I'd like to
suggest following approach:
instead of doing this differentiation inside each function, the
functions should always return there result as list of strings. If
printing is required, meaning, the function is not called from outside
and not from another function, the printing is done by shell.py:postcmd,
which will be called after all commands called from command line.

If a function return a list of string, these will be printed to stdout.
If not, it behaves like before. There is no immediate need to change the
existing function, but of course, this would make the code cleaner.

I've implemented this in patch TODO.

Additionaly, I've implemented the functions
activationkey.py:def do_activationkey_diff
configchannel.py:def do_configchannel_diff
kickstart.py:def do_kickstart_diff
softwarechannel.py:def do_softwarechannel_diff
to show differences in between two components.

I'd be glad, if this patches will be applied to spacecmd.

regards,
Jörg Steffens

- -- 
  Jörg Steffens            joerg.steff...@dass-it.de
  dass IT GmbH             Phone: +49.221.3565666-91
  http://www.dass-IT.de      Fax: +49.221.3565666-10

 Sitz der Gesellschaft: Köln | Amtsgericht Köln: HRB52500
 Geschäftsführer: S. Dühr, M. Außendorf, Jörg Steffens, P. Storz

-----BEGIN PGP SIGNATURE-----
Version: GnuPG v2.0.18 (GNU/Linux)
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org/

iEYEARECAAYFAk+sKDwACgkQebMQ0f2L2xuYowCgkjr2AMzJ8gMDA3RRB1e8A+1S
TPIAnRWKjya9H0+V2zyyBtKVtZ6ApALR
=EMo5
-----END PGP SIGNATURE-----
From: Joerg Steffens <joerg.steff...@dass-it.de>
Date: Thu, 10 May 2012 21:21:08 +0200
Subject: [PATCH] print return values

---

diff --git a/spacecmd/src/bin/spacecmd b/spacecmd/src/bin/spacecmd
index 6b11f5b..29970ab 100755
--- a/spacecmd/src/bin/spacecmd
+++ b/spacecmd/src/bin/spacecmd
@@ -122,7 +122,8 @@ if __name__ == '__main__':
                 command += ' %s' % ' '.join("'%s'" % s for s in args[1:])
 
             # run the command
-            shell.onecmd(shell.precmd(command))
+            precmd = shell.precmd(command)
+            shell.print_result(shell.onecmd( precmd ), precmd)
         except KeyboardInterrupt:
             print
             print 'User Interrupt'
diff --git a/spacecmd/src/lib/shell.py b/spacecmd/src/lib/shell.py
index 15b7277..08e4d33 100644
--- a/spacecmd/src/lib/shell.py
+++ b/spacecmd/src/lib/shell.py
@@ -188,7 +188,18 @@ class SpacewalkShell(Cmd):
 
 
     # update the prompt with the SSM size
-    def postcmd(self, stop, line):
+    def postcmd(self, cmdresult, cmd):
+        self.print_result( cmdresult, cmd )
         self.prompt = re.sub('##', str(len(self.ssm)), self.prompt_template)
 
+    def print_result( self, cmdresult, cmd ):
+        logging.debug( cmd + ": " + repr(cmdresult) )
+        if cmd:
+            try:
+                for i in cmdresult:
+                    print i
+            except TypeError:
+                pass
+
+
 # vim:ts=4:expandtab:
From: Joerg Steffens <joerg.steff...@dass-it.de>
Date: Thu, 10 May 2012 21:21:08 +0200
Subject: [PATCH] add do_SPACEWALKCOMPONENT_diff functions

---

diff --git a/spacecmd/src/lib/activationkey.py b/spacecmd/src/lib/activationkey.py
index 2910333..41406ee 100644
--- a/spacecmd/src/lib/activationkey.py
+++ b/spacecmd/src/lib/activationkey.py
@@ -743,6 +743,7 @@ def do_activationkey_details(self, args):
 
     add_separator = False
 
+    result = []
     for key in args:
         try:
             details = self.client.activationkey.getDetails(self.session,
@@ -780,46 +781,47 @@ def do_activationkey_details(self, args):
         if add_separator: print self.SEPARATOR
         add_separator = True
 
-        print 'Key:                    %s' % details.get('key')
-        print 'Description:            %s' % details.get('description')
-        print 'Universal Default:      %s' % details.get('universal_default')
-        print 'Usage Limit:            %s' % details.get('usage_limit')
-        print 'Deploy Config Channels: %s' % config_channel_deploy
+        result.append( 'Key:                    %s' % details.get('key') )
+        result.append( 'Description:            %s' % details.get('description') )
+        result.append( 'Universal Default:      %s' % details.get('universal_default') )
+        result.append( 'Usage Limit:            %s' % details.get('usage_limit') )
+        result.append( 'Deploy Config Channels: %s' % config_channel_deploy )
 
-        print
-        print 'Software Channels'
-        print '-----------------'
-        print details.get('base_channel_label')
+        result.append( '' )
+        result.append( 'Software Channels' )
+        result.append( '-----------------' )
+        result.append( details.get('base_channel_label') )
 
         for channel in sorted(details.get('child_channel_labels')):
-            print ' |-- %s' % channel
+            result.append( ' |-- %s' % channel )
 
-        print
-        print 'Configuration Channels'
-        print '----------------------'
+        result.append( '' )
+        result.append( 'Configuration Channels' )
+        result.append( '----------------------' )
         for channel in config_channels:
-            print channel.get('label')
+            result.append( channel.get('label') )
 
-        print
-        print 'Entitlements'
-        print '------------'
-        print '\n'.join(sorted(details.get('entitlements')))
+        result.append( '' )
+        result.append( 'Entitlements' )
+        result.append( '------------' )
+        result.append( '\n'.join(sorted(details.get('entitlements'))) )
 
-        print
-        print 'System Groups'
-        print '-------------'
-        print '\n'.join(sorted(groups))
+        result.append( '' )
+        result.append( 'System Groups' )
+        result.append( '-------------' )
+        result.append( '\n'.join(sorted(groups)) )
 
-        print
-        print 'Packages'
-        print '--------'
+        result.append( '' )
+        result.append( 'Packages' )
+        result.append( '--------' )
         for package in sorted(details.get('packages')):
             name = package.get('name')
 
             if package.get('arch'):
                 name += '.%s' % package.get('arch')
 
-            print name
+            result.append( name )
+    return result
 
 ####################
 
@@ -1357,4 +1359,73 @@ def do_activationkey_clone(self, args):
             logging.error("Failed to clone %s to %s" % \
              (ak, keydetails['key']))
 
+####################
+# activationkey helper
+
+def is_activationkey( self, name ):
+    if not name: return
+    return name in self.do_activationkey_list( name, True )
+
+def check_activationkey( self, name ):
+    if not name:
+        logging.error( "no activationkey label given" )
+        return False
+    if not self.is_activationkey( name ):
+        logging.error( "invalid activationkey label " + name )
+        return False
+    return True
+
+def dump_activationkey(self, name, replacedict=None, excludes=[ "Universal Default:" ]):
+    content = self.do_activationkey_details( name )
+
+    content = get_normalized_text( content, replacedict=replacedict, excludes=excludes )
+
+    return content
+
+####################
+
+def help_activationkey_diff(self):
+    print 'activationkeyt_diff: diff activationkeys'
+    print ''
+    print 'usage: activationkey_diff SOURCE_ACTIVATIONKEY TARGET_ACTIVATIONKEY'
+
+def complete_activationkey_diff(self, text, line, beg, end):
+    parts = shlex.split(line)
+    if line[-1] == ' ': parts.append('')
+    args = len(parts)
+
+    if args == 2:
+        return tab_completer(self.do_activationkey_list('', True), text)
+    if args == 3:
+        return tab_completer(self.do_activationkey_list('', True), text)
+    return []
+
+def do_activationkey_diff(self, args):
+    options = []
+
+    (args, options) = parse_arguments(args, options)
+
+    if len(args) != 1 and len(args) != 2:
+        self.help_activationkey_diff()
+        return
+
+    source_channel = args[0]
+    if not self.check_activationkey( source_channel ): return
+
+    target_channel = None
+    if len(args) == 2:
+        target_channel = args[1]
+    elif hasattr( self, "do_activationkey_getcorresponding" ):
+        # can a corresponding channel name be found automatically?
+        target_channel=self.do_activationkey_getcorresponding( source_channel )
+    if not self.check_activationkey( target_channel ): return
+
+    source_replacedict, target_replacedict = get_string_diff_dicts( source_channel, target_channel )
+
+    source_data = self.dump_activationkey( source_channel, source_replacedict )
+    target_data = self.dump_activationkey( target_channel, target_replacedict )
+
+    return diff( source_data, target_data, source_channel, target_channel )
+
+
 # vim:ts=4:expandtab:
diff --git a/spacecmd/src/lib/configchannel.py b/spacecmd/src/lib/configchannel.py
index 7410b95..884970a 100644
--- a/spacecmd/src/lib/configchannel.py
+++ b/spacecmd/src/lib/configchannel.py
@@ -153,32 +153,35 @@ def do_configchannel_filedetails(self, args):
         # grab the first item since we only do one file
         details = results[0]
 
-    print 'Path:     %s' % details.get('path')
-    print 'Type:     %s' % details.get('type')
-    print 'Revision: %i' % details.get('revision')
-    print 'Created:  %s' % details.get('creation')
-    print 'Modified: %s' % details.get('modified')
+    result = []
+    result.append( 'Path:     %s' % details.get('path') )
+    result.append( 'Type:     %s' % details.get('type') )
+    result.append( 'Revision: %i' % details.get('revision') )
+    result.append( 'Created:  %s' % details.get('creation') )
+    result.append( 'Modified: %s' % details.get('modified') )
 
     if details.get('type') == 'symlink':
-        print
-        print 'Target Path:     %s' % details.get('target_path')
+        result.append( '' )
+        result.append( 'Target Path:     %s' % details.get('target_path') )
     else:
-        print
-        print 'Owner:           %s' % details.get('owner')
-        print 'Group:           %s' % details.get('group')
-        print 'Mode:            %s' % details.get('permissions_mode')
+        result.append( '' )
+        result.append( 'Owner:           %s' % details.get('owner') )
+        result.append( 'Group:           %s' % details.get('group') )
+        result.append( 'Mode:            %s' % details.get('permissions_mode') )
 
-    print 'SELinux Context: %s' % details.get('selinux_ctx')
+    result.append( 'SELinux Context: %s' % details.get('selinux_ctx') )
 
     if details.get('type') == 'file':
-        print 'MD5:             %s' % details.get('md5')
-        print 'Binary:          %s' % details.get('binary')
+        result.append( 'MD5:             %s' % details.get('md5') )
+        result.append( 'Binary:          %s' % details.get('binary') )
 
         if not details.get('binary'):
-            print
-            print 'Contents'
-            print '--------'
-            print details.get('contents')
+            result.append( '' )
+            result.append( 'Contents' )
+            result.append( '--------' )
+            result.append( details.get('contents') )
+
+    return result
 
 ####################
 
@@ -289,6 +292,7 @@ def do_configchannel_details(self, args):
 
     add_separator = False
 
+    result = []
     for channel in args:
         details = self.client.configchannel.getDetails(self.session,
                                                        channel)
@@ -299,15 +303,16 @@ def do_configchannel_details(self, args):
         if add_separator: print self.SEPARATOR
         add_separator = True
 
-        print 'Label:       %s' % details.get('label')
-        print 'Name:        %s' % details.get('name')
-        print 'Description: %s' % details.get('description')
+        result.append( 'Label:       %s' % details.get('label') )
+        result.append( 'Name:        %s' % details.get('name') )
+        result.append( 'Description: %s' % details.get('description') )
 
-        print
-        print 'Files'
-        print '-----'
+        result.append( '' )
+        result.append( 'Files' )
+        result.append( '-----' )
         for f in files:
-            print f.get('path')
+            result.append( f.get('path') )
+    return result
 
 ####################
 
@@ -1100,4 +1105,79 @@ def do_configchannel_clone(self, args):
             logging.error("Failed to clone %s to %s" % \
              (cc, ccdetails['label']))
 
+####################
+# configchannel helper
+
+def is_configchannel( self, name ):
+    if not name: return
+    return name in self.do_configchannel_list( name, True )
+
+def check_configchannel( self, name ):
+    if not name:
+        logging.error( "no configchannel given" )
+        return False
+    if not self.is_configchannel( name ):
+        logging.error( "invalid configchannel label " + name )
+        return False
+    return True
+
+def dump_configchannel_filedetails(self, name, filename):
+    content = self.do_configchannel_filedetails( name +" "+ filename )
+    return content
+
+def dump_configchannel(self, name, replacedict=None, excludes=[ "Revision:", "Created:", "Modified:" ]):
+    content = self.do_configchannel_details( name )
+
+    for filename in self.do_configchannel_listfiles(name, True):
+        content.extend( self.dump_configchannel_filedetails(name, filename) )
+
+    content = get_normalized_text( content, replacedict=replacedict, excludes=excludes )
+
+    return content
+
+####################
+
+def help_configchannel_diff(self):
+    print 'configchannel_diff: diff between config channels'
+    print ''
+    print 'usage: configchannel_diff SOURCE_CHANNEL TARGET_CHANNEL'
+
+def complete_configchannel_diff(self, text, line, beg, end):
+    parts = shlex.split(line)
+    if line[-1] == ' ': parts.append('')
+    args = len(parts)
+
+    if args == 2:
+        return tab_completer(self.do_configchannel_list('', True), text)
+    if args == 3:
+        return tab_completer(self.do_configchannel_list('', True), text)
+    return []
+
+def do_configchannel_diff(self, args):
+    options = []
+
+    (args, options) = parse_arguments(args, options)
+
+    if len(args) != 1 and len(args) != 2:
+        self.help_stage_configchannel_diff()
+        return
+
+    source_channel = args[0]
+    if not self.check_configchannel( source_channel ): return
+
+    target_channel = None
+    if len(args) == 2:
+        target_channel = args[1]
+    elif hasattr( self, "do_configchannel_getcorresponding" ):
+        # can a corresponding channel name be found automatically?
+        target_channel=self.do_configchannel_getcorresponding( source_channel )
+    if not self.check_configchannel( target_channel ): return
+
+    source_replacedict, target_replacedict = get_string_diff_dicts( source_channel, target_channel )
+
+    source_data = self.dump_configchannel( source_channel, source_replacedict )
+    target_data = self.dump_configchannel( target_channel, target_replacedict )
+
+    return diff( source_data, target_data, source_channel, target_channel )
+
 # vim:ts=4:expandtab:
diff --git a/spacecmd/src/lib/kickstart.py b/spacecmd/src/lib/kickstart.py
index fb51df5..ef622ec 100644
--- a/spacecmd/src/lib/kickstart.py
+++ b/spacecmd/src/lib/kickstart.py
@@ -287,8 +287,11 @@ def do_kickstart_details(self, args):
         self.client.kickstart.profile.keys.getActivationKeys(self.session,
                                                              label)
 
-    variables = self.client.kickstart.profile.getVariables(self.session,
-                                                           label)
+    try:
+        variables = self.client.kickstart.profile.getVariables(self.session,
+                                                      label)
+    except:
+        variables = []
 
     tree = \
         self.client.kickstart.tree.getDetails(self.session,
@@ -336,100 +339,103 @@ def do_kickstart_details(self, args):
     scripts = self.client.kickstart.profile.listScripts(self.session,
                                                         label)
 
-    print 'Name:        %s' % kickstart.get('name')
-    print 'Label:       %s' % kickstart.get('label')
-    print 'Tree:        %s' % kickstart.get('tree_label')
-    print 'Active:      %s' % kickstart.get('active')
-    print 'Advanced:    %s' % kickstart.get('advanced_mode')
-    print 'Org Default: %s' % kickstart.get('org_default')
+    result = []
+    result.append( 'Name:        %s' % kickstart.get('name') )
+    result.append( 'Label:       %s' % kickstart.get('label') )
+    result.append( 'Tree:        %s' % kickstart.get('tree_label') )
+    result.append( 'Active:      %s' % kickstart.get('active') )
+    result.append( 'Advanced:    %s' % kickstart.get('advanced_mode') )
+    result.append( 'Org Default: %s' % kickstart.get('org_default') )
 
-    print
-    print 'Configuration Management: %s' % config_manage
-    print 'Remote Commands:          %s' % remote_commands
+    result.append( '' )
+    result.append( 'Configuration Management: %s' % config_manage )
+    result.append( 'Remote Commands:          %s' % remote_commands )
 
-    print
-    print 'Software Channels'
-    print '-----------------'
-    print base_channel.get('label')
+    result.append( '' )
+    result.append( 'Software Channels' )
+    result.append( '-----------------' )
+    result.append( base_channel.get('label') )
 
     for channel in sorted(child_channels):
-        print '  |-- %s' % channel
+        result.append( '  |-- %s' % channel )
 
     if len(advanced_options):
-        print
-        print 'Advanced Options'
-        print '----------------'
+        result.append( '' )
+        result.append( 'Advanced Options' )
+        result.append( '----------------' )
         for o in sorted(advanced_options, key=itemgetter('name')):
             if o.get('arguments'):
-                print '%s %s' % (o.get('name'), o.get('arguments'))
+                result.append( '%s %s' % (o.get('name'), o.get('arguments')) )
 
     if len(custom_options):
-        print
-        print 'Custom Options'
-        print '--------------'
+        result.append( '' )
+        result.append( 'Custom Options' )
+        result.append( '--------------' )
         for o in sorted(custom_options, key=itemgetter('arguments')):
-            print re.sub('\n', '', o.get('arguments'))
+            result.append( re.sub('\n', '', o.get('arguments')) )
 
     if len(partitions):
-        print
-        print 'Partitioning'
-        print '------------'
-        print '\n'.join(partitions)
+        result.append( '' )
+        result.append( 'Partitioning' )
+        result.append( '------------' )
+        result.append( '\n'.join(partitions) )
 
-    print
-    print 'Software'
-    print '--------'
-    print '\n'.join(software)
+    result.append( '' )
+    result.append( 'Software' )
+    result.append( '--------' )
+    result.append( '\n'.join(software) )
 
     if len(act_keys):
-        print
-        print 'Activation Keys'
-        print '---------------'
+        result.append( '' )
+        result.append( 'Activation Keys' )
+        result.append( '---------------' )
         for k in sorted(act_keys, key=itemgetter('key')):
-            print k.get('key')
+            result.append( k.get('key') )
 
     if len(crypto_keys):
-        print
-        print 'Crypto Keys'
-        print '-----------'
+        result.append( '' )
+        result.append( 'Crypto Keys' )
+        result.append( '-----------' )
         for k in sorted(crypto_keys, key=itemgetter('description')):
-            print k.get('description')
+            result.append( k.get('description') )
 
     if len(file_preservations):
-        print
-        print 'File Preservations'
-        print '------------------'
+        result.append( '' )
+        result.append( 'File Preservations' )
+        result.append( '------------------' )
         for fp in sorted(file_preservations, key=itemgetter('name')):
-            print fp.get('name')
+            result.append( fp.get('name') )
             for profile_name in sorted(fp.get('file_names')):
-                print '    |-- %s' % profile_name
+                result.append( '    |-- %s' % profile_name )
 
     if len(variables):
-        print
-        print 'Variables'
-        print '---------'
+        result.append( '' )
+        result.append( 'Variables' )
+        result.append( '---------' )
         for k in sorted(variables.keys()):
-            print '%s = %s' % (k, str(variables[k]))
+            result.append( '%s = %s' % (k, str(variables[k])) )
 
     if len(scripts):
-        print
-        print 'Scripts'
-        print '-------'
+        result.append( '' )
+        result.append( 'Scripts' )
+        result.append( '-------' )
 
         add_separator = False
 
         for s in scripts:
-            if add_separator: print self.SEPARATOR
+            if add_separator: result.append( self.SEPARATOR )
             add_separator = True
 
-            print 'Type:        %s' % s.get('script_type')
-            print 'Chroot:      %s' % s.get('chroot')
+            result.append( 'Type:        %s' % s.get('script_type') )
+            result.append( 'Chroot:      %s' % s.get('chroot') )
 
             if s.get('interpreter'):
-                print 'Interpreter: %s' % s.get('interpreter')
+                result.append( 'Interpreter: %s' % s.get('interpreter') )
 
-            print
-            print s.get('contents')
+            result.append( '' )
+            result.append( s.get('contents') )
+
+    return result
 
 ####################
 
@@ -2136,4 +2142,72 @@ def import_kickstart_fromdetails(self, ksdetails):
             ksdetails['post_kopts'])
     return True
 
+####################
+# kickstart helper
+
+def is_kickstart( self, name ):
+    if not name: return
+    return name in self.do_kickstart_list( name, True )
+
+def check_kickstart( self, name ):
+    if not name:
+        logging.error( "no kickstart label given" )
+        return False
+    if not self.is_kickstart( name ):
+        logging.error( "invalid kickstart label " + name )
+        return False
+    return True
+
+def dump_kickstart(self, name, replacedict=None, excludes=[ "Org Default:" ]):
+    content = self.do_kickstart_details( name )
+
+    content = get_normalized_text( content, replacedict=replacedict, excludes=excludes )
+
+    return content
+
+####################
+
+def help_kickstart_diff(self):
+    print 'kickstart_diff: diff kickstart files'
+    print ''
+    print 'usage: kickstart_diff SOURCE_CHANNEL TARGET_CHANNEL'
+
+def complete_kickstart_diff(self, text, line, beg, end):
+    parts = shlex.split(line)
+    if line[-1] == ' ': parts.append('')
+    args = len(parts)
+
+    if args == 2:
+        return tab_completer(self.do_kickstart_list('', True), text)
+    if args == 3:
+        return tab_completer(self.do_kickstart_list('', True), text)
+    return []
+
+def do_kickstart_diff(self, args):
+    options = []
+
+    (args, options) = parse_arguments(args, options)
+
+    if len(args) != 1 and len(args) != 2:
+        self.help_kickstart_diff()
+        return
+
+    source_channel = args[0]
+    if not self.check_kickstart( source_channel ): return
+
+    target_channel = None
+    if len(args) == 2:
+        target_channel = args[1]
+    elif hasattr( self, "do_kickstart_getcorresponding" ):
+        # can a corresponding channel name be found automatically?
+        target_channel=self.do_kickstart_getcorresponding( source_channel)
+    if not self.check_kickstart( target_channel ): return
+
+    source_replacedict, target_replacedict = get_string_diff_dicts( source_channel, target_channel )
+
+    source_data = self.dump_kickstart( source_channel, source_replacedict )
+    target_data = self.dump_kickstart( target_channel, target_replacedict )
+
+    return diff( source_data, target_data, source_channel, target_channel )
+
 # vim:ts=4:expandtab:
diff --git a/spacecmd/src/lib/softwarechannel.py b/spacecmd/src/lib/softwarechannel.py
index 48ddc0c..a4e0598 100644
--- a/spacecmd/src/lib/softwarechannel.py
+++ b/spacecmd/src/lib/softwarechannel.py
@@ -1399,7 +1399,7 @@ def do_softwarechannel_regenerateyumcache(self, args):
     if not len(args):
         self.help_softwarechannel_regenerateyumcache()
         return
-    
+
     # allow globbing of software channel names
     channels = filter_results(self.do_softwarechannel_list('', True), args)
 
@@ -1407,4 +1407,72 @@ def do_softwarechannel_regenerateyumcache(self, args):
         logging.debug('Regenerating YUM cache for %s' % channel)
         self.client.channel.software.regenerateYumCache(self.session, channel)
 
+####################
+# softwarechannel helper
+
+def is_softwarechannel( self, name ):
+    if not name: return
+    return name in self.do_softwarechannel_list( name, True )
+
+def check_softwarechannel( self, name ):
+    if not name:
+        logging.error( "no softwarechannel label given" )
+        return False
+    if not self.is_softwarechannel( name ):
+        logging.error( "invalid softwarechannel label " + name )
+        return False
+    return True
+
+def dump_softwarechannel(self, name, replacedict=None, excludes=[]):
+    content = self.do_softwarechannel_listallpackages( name, doreturn=True )
+
+    content = get_normalized_text( content, replacedict=replacedict, excludes=excludes )
+
+    return content
+
+####################
+
+def help_softwarechannel_diff(self):
+    print 'softwarechannel_diff: diff softwarechannel files'
+    print ''
+    print 'usage: softwarechannel_diff SOURCE_CHANNEL TARGET_CHANNEL'
+
+def complete_softwarechannel_diff(self, text, line, beg, end):
+    parts = shlex.split(line)
+    if line[-1] == ' ': parts.append('')
+    args = len(parts)
+
+    if args == 2:
+        return tab_completer(self.do_softwarechannel_list('', True), text)
+    if args == 3:
+        return tab_completer(self.do_softwarechannel_list('', True), text)
+    return []
+
+def do_softwarechannel_diff(self, args):
+    options = []
+
+    (args, options) = parse_arguments(args, options)
+
+    if len(args) != 1 and len(args) != 2:
+        self.help_softwarechannel_diff()
+        return
+
+    source_channel = args[0]
+    if not self.check_softwarechannel( source_channel ): return
+
+    target_channel = None
+    if len(args) == 2:
+        target_channel = args[1]
+    elif hasattr( self, "do_softwarechannel_getcorresponding" ):
+        # can a corresponding channel name be found automatically?
+        target_channel=self.do_softwarechannel_getcorresponding( source_channel)
+    if not self.check_softwarechannel( target_channel ): return
+
+    # softwarechannel do not contain references to other components,
+    # therefore there is no need to use replace dicts
+    source_data = self.dump_softwarechannel( source_channel, None )
+    target_data = self.dump_softwarechannel( target_channel, None )
+
+    return diff( source_data, target_data, source_channel, target_channel )
+
 # vim:ts=4:expandtab:
diff --git a/spacecmd/src/lib/utils.py b/spacecmd/src/lib/utils.py
index 6292f5a..0a7c4c5 100644
--- a/spacecmd/src/lib/utils.py
+++ b/spacecmd/src/lib/utils.py
@@ -23,6 +23,7 @@
 
 import logging, os, pickle, re, readline, shlex, sys, time, xmlrpclib
 from datetime import datetime, timedelta
+from difflib  import unified_diff, SequenceMatcher
 from optparse import OptionParser
 from tempfile import mkstemp
 from textwrap import wrap
@@ -633,6 +634,67 @@ def json_read_from_file(filename):
             print "could not open file %s for reading, check permissions?" % filename
         return None
 
+def get_string_diff_dicts( string1, string2 ):
+    replace1 = {}
+    replace2 = {}
+    s = SequenceMatcher(None, string1, string2)
+    for tag, i1a, i1b, i2a, i2b in s.get_opcodes():
+        sub1=string1[i1a:i1b]
+        sub2=string2[i2a:i2b]
+        if tag == "equal":
+            # equal, nothing to do
+            pass
+        elif tag == "replace":
+            replace1[sub1] = "DIFF("+sub1+"|"+sub2+")"
+            replace2[sub2] = "DIFF("+sub1+"|"+sub2+")"
+        else:
+            # "insert" or "delete"
+            # can't handle this
+            #logging.debug( "will not handle differences " + sub1 + sub2 )
+            logging.debug( "Too many differences between " + string1 + " and " + string2 + ". Skipping usage of common strings." )
+            return [None,None]
+    return [replace1,replace2]
+
+def replace( line, replacedict ):
+    if replacedict:
+        for source in replacedict:
+            line = line.replace( source, replacedict[source] )
+    return line
+
+def get_normalized_text( text, replacedict=None, excludes=_DIFF_EXCLUDES ):
+    # parts of the data inside the spacewalk component information
+    # is not relevant for showing real differences between two instances.
+    # Therefore parts of the data will be modified before the real diff:
+    # - specific lines, starting with a defined keyword, will be excluded
+    # - specific character sequences will be replaced with the same text in both instances.
+    # Example:
+    # we want to compare two activationkeys from different stages:
+    # "1-rhel6-x86_64-dev" and "1-rhel6-x86_64-prd"
+    # ("dev" for "development" and "prd" for "production").
+    # We assume that the "dev" activationkey "1-rhel6-x86_64-dev"
+    # has references to other "dev" components,
+    # while the "prd" activationkey "1-rhel6-x86_64-prd"
+    # has references to other "prd" components.
+    # Therefore we replace all occurrences of "dev" in "1-rhel6-x86_64-dev"
+    # and all occurrences of "prd" in "1-rhel6-x86_64-prd"
+    # with the common string "DIFF(dev|prd)".
+    # What differences are to be replaced is guessed
+    # from the name differences of there components.
+    # This will not work always, but it help in a lot of cases.
+
+    normalized_text = []
+    if text:
+        for string in text:
+            for line in string.split( "\n" ):
+                if not excludes or not line.startswith( tuple(excludes) ):
+                    normalized_text.append( replace( line, replacedict ) )
+                else:
+                    logging.debug( "excluding line: " + line )
+    return normalized_text
+
+def diff( source_data, target_data, source_channel, target_channel ):
+    return unified_diff( source_data, target_data, source_channel, target_channel )
+
 def file_needs_b64_enc(self, contents):
 
     # Used to check if files (config files primarily) need base64 encoding 
_______________________________________________
Spacewalk-devel mailing list
Spacewalk-devel@redhat.com
https://www.redhat.com/mailman/listinfo/spacewalk-devel

Reply via email to