Package config_management :: Module rhncfg_diff
[hide private]
[frames] | no frames]

Source Code for Module config_management.rhncfg_diff

  1  # 
  2  # Copyright (c) 2008--2016 Red Hat, Inc. 
  3  # 
  4  # This software is licensed to you under the GNU General Public License, 
  5  # version 2 (GPLv2). There is NO WARRANTY for this software, express or 
  6  # implied, including the implied warranties of MERCHANTABILITY or FITNESS 
  7  # FOR A PARTICULAR PURPOSE. You should have received a copy of GPLv2 
  8  # along with this software; if not, see 
  9  # http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. 
 10  # 
 11  # Red Hat trademarks are not licensed under GPLv2. No permission is 
 12  # granted to use or replicate Red Hat trademarks that are incorporated 
 13  # in this software or its documentation. 
 14  # 
 15   
 16  import base64 
 17  from datetime import datetime 
 18  import difflib 
 19  import os 
 20  import sys 
 21   
 22  from config_common import handler_base, utils, cfg_exceptions 
 23  from config_common.rhn_log import log_debug, die 
 24  from config_common.file_utils import f_date, ostr_to_sym 
 25  from spacewalk.common.usix import next 
 26  from rhn.i18n import bstr, sstr 
 27   
 28   
29 -class Handler(handler_base.HandlerBase):
30 _usage_options = "[options] file [ file ... ]" 31 _options_table = [ 32 handler_base.HandlerBase._option_class( 33 '-c', '--channel', action="store", 34 help="Get file(s) from this config channel", 35 ), 36 handler_base.HandlerBase._option_class( 37 '-r', '--revision', action="store", 38 help="Use this revision", 39 ), 40 handler_base.HandlerBase._option_class( 41 '-d', '--dest-file', action="store", 42 help="Remote file to compare to", 43 ), 44 handler_base.HandlerBase._option_class( 45 '-t', '--topdir', action="store", 46 help="Directory to which all file paths are relative", 47 ), 48 ]
49 - def run(self):
50 log_debug(2) 51 52 if self.options.dest_file and self.options.topdir: 53 die(6, "Error: conflicting options --dest-file and --topdir") 54 55 if len(self.args) == 0: 56 die(0, "No files supplied (use --help for help)") 57 58 channel = self.options.channel 59 60 if not channel: 61 die(6, "Config channel not specified") 62 63 r = self.repository 64 if not r.config_channel_exists(channel): 65 die(6, "Error: config channel %s does not exist" % channel) 66 67 topdir = self.options.topdir 68 revision = self.options.revision 69 70 files_to_diff = [] 71 72 files = [utils.normalize_path(x) for x in self.args] 73 files_count = len(files) 74 75 if files_count != 1 and revision is not None: 76 die(8, "--revision can only be used with a single file") 77 78 if self.options.dest_file: 79 if files_count != 1: 80 die(7, "--dest-file accepts a single file") 81 82 files_to_diff.append((files[0], self.options.dest_file)) 83 84 elif topdir: 85 if not os.path.isdir(topdir): 86 die(8, "--topdir specified, but `%s' not a directory" % 87 topdir) 88 89 #5/11/04 wregglej - 141790 remove trailing slash in topdir, if present. 90 topdir = utils.rm_trailing_slash(topdir) 91 92 for f in files: 93 if not f.startswith(topdir): 94 die(8, "--topdir %s specified, but file `%s' doesn't comply" 95 % (topdir, f)) 96 if os.path.isdir(f) and not os.path.islink(f): 97 die(8, "Cannot diff %s; it is a directory" % f) 98 files_to_diff.append((f, f[len(topdir):])) 99 else: 100 for f in files: 101 if os.path.isdir(f) and not os.path.islink(f): 102 die(8, "Cannot diff %s; it is a directory" % f) 103 files_to_diff.append((f, f)) 104 105 for (local_file, remote_file) in files_to_diff: 106 sys.stdout.write( 107 self.diff_file(channel, remote_file, local_file, revision))
108
109 - def __attributes_differ(self, fsrc, fdst):
110 """ Returns true if acl, ownership, type or selinux context differ. 111 fsrc is config file retrieved from xmlrpc, fdst is output of make_stat_info() 112 """ 113 return (fsrc['filemode'] != fdst['mode']) or \ 114 (fsrc['username'] != fdst['user']) or (fsrc['groupname'] != fdst['group']) or \ 115 (fsrc['selinux_ctx'] != fdst['selinux_ctx'])
116
117 - def diff_file(self, channel, path, local_file, revision):
118 r = self.repository 119 try: 120 info = r.get_raw_file_info(channel, path, revision) 121 if 'encoding' in info and info['file_contents']: 122 if info['encoding'] == 'base64': 123 info['file_contents'] = base64.decodestring(bstr(info['file_contents'])) 124 else: 125 die(9, 'Error: unknown encoding %s' % info['encoding']) 126 except cfg_exceptions.RepositoryFileMissingError: 127 die(2, "Error: no such file %s (revision %s) in config channel %s" 128 % (path, revision, channel)) 129 if os.path.islink(local_file) and info['filetype'] != 'symlink' : 130 die(8, "Cannot diff %s; the file on the system is a symbolic link while the file in the channel is not. " % local_file) 131 if info['filetype'] == 'symlink' and not os.path.islink(local_file) : 132 die(8, "Cannot diff %s; the file on the system is not a symbolic link while the file in the channel is. " % local_file) 133 if info['filetype'] == 'symlink': 134 src_link = info['symlink'] 135 dest_link = os.readlink(local_file) 136 if src_link != os.readlink(local_file): 137 return "Symbolic links differ. Channel: '%s' -> '%s' System: '%s' -> '%s' \n " % (path,src_link, path, dest_link) 138 return "" 139 140 response_output = "" 141 content_differs = False 142 if 'is_binary' in info and info['is_binary'] == 'Y': 143 from_content = info['file_contents'] 144 to_file = open(local_file, 'rb') 145 to_content = to_file.read() 146 to_file.close() 147 if len(from_content) != len(to_content): 148 content_differs = True 149 else: 150 for i in range(len(from_content)): 151 if from_content[i] != to_content[i]: 152 content_differs = True 153 break 154 if content_differs: 155 response_output = "Binary file content differs\n" 156 else: 157 fromlines = sstr(info['file_contents']).splitlines(1) 158 tofile = open(local_file, 'r') 159 tolines = tofile.readlines() 160 tofile.close() 161 diff_output = difflib.unified_diff(fromlines, tolines, info['path'], local_file) 162 first_row = second_row = '' 163 try: 164 first_row = next(diff_output) 165 # if content was same, exception thrown so following 166 # lines don't execute 167 content_differs = True 168 second_row = next(diff_output) 169 response_output = ''.join(list(diff_output)) 170 except StopIteration: 171 pass 172 173 file_stat = os.lstat(local_file) 174 local_info = r.make_stat_info(local_file, file_stat) 175 # rhel4 do not support selinux 176 if not 'selinux_ctx' in local_info: 177 local_info['selinux_ctx'] = '' 178 if 'selinux_ctx' not in info: 179 info['selinux_ctx'] = '' 180 if not content_differs and not self.__attributes_differ(info, local_info): 181 return "" 182 else: 183 template = "%s %s\t%s\tattributes: %s %s %s %s\tconfig channel: %s\trevision: %s" 184 if 'modified' not in info: 185 info['modified'] = '' 186 first_row = template % ('---', path, str(info['modified']), ostr_to_sym(info['filemode'], info['filetype']), 187 info['username'], info['groupname'], info['selinux_ctx'], channel, 188 info['revision'], 189 ) 190 second_row = template % ('+++', local_file, f_date(datetime.fromtimestamp(local_info['mtime'])), ostr_to_sym(local_info['mode'], 'file'), 191 local_info['user'], local_info['group'], local_info['selinux_ctx'], 'local file', None 192 ) 193 return first_row + '\n' + second_row + '\n' + response_output
194