Package backend :: Package server :: Package handlers :: Package config_mgmt :: Module rhn_config_management
[hide private]
[frames] | no frames]

Source Code for Module backend.server.handlers.config_mgmt.rhn_config_management

  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  # Config file handler (management tool) 
 17  # 
 18   
 19  import sys 
 20  import difflib 
 21  from spacewalk.common.rhnLog import log_debug 
 22  from spacewalk.common.usix import raise_with_tb, next 
 23  from spacewalk.common.rhnException import rhnFault 
 24  from spacewalk.server import rhnSQL, configFilesHandler 
 25  from spacewalk.common.fileutils import f_date, ostr_to_sym 
 26   
 27   
28 -class ConfigManagement(configFilesHandler.ConfigFilesHandler):
29
30 - def __init__(self):
31 log_debug(3) 32 configFilesHandler.ConfigFilesHandler.__init__(self) 33 self.functions.update({ 34 'management.get_file': 'management_get_file', 35 'management.list_config_channels': 'management_list_channels', 36 'management.create_config_channel': 'management_create_channel', 37 'management.remove_config_channel': 'management_remove_channel', 38 'management.list_file_revisions': 'management_list_file_revisions', 39 'management.list_files': 'management_list_files', 40 'management.has_file': 'management_has_file', 41 'management.put_file': 'management_put_file', 42 'management.remove_file': 'management_remove_file', 43 'management.diff': 'management_diff', 44 'management.get_default_delimiters': 'management_get_delimiters', 45 'management.get_maximum_file_size': 'management_get_maximum_file_size', 46 }) 47 self.user = None 48 self.default_delimiter = '@'
49 50 _query_list_config_channels = rhnSQL.Statement(""" 51 select cc.name, 52 cc.label, 53 cct.label channel_type 54 from rhnConfigChannelType cct, 55 rhnConfigChannel cc 56 where cc.org_id = :org_id 57 and cc.confchan_type_id = cct.id 58 and cct.label = 'normal' 59 order by cc.label, cc.name 60 """) 61
62 - def _get_and_validate_session(self, dict):
63 session = dict.get('session') 64 self._validate_session(session)
65
66 - def management_list_channels(self, dict):
67 log_debug(1) 68 self._get_and_validate_session(dict) 69 return [x['label'] for x in rhnSQL.fetchall_dict(self._query_list_config_channels, 70 org_id=self.org_id) or []]
71 72 _query_lookup_config_channel = rhnSQL.Statement(""" 73 select id 74 from rhnConfigChannel 75 where org_id = :org_id 76 and label = :config_channel 77 """) 78
79 - def management_create_channel(self, dict):
80 log_debug(1) 81 self._get_and_validate_session(dict) 82 83 config_channel = dict.get('config_channel') 84 # XXX Validate the namespace 85 86 config_channel_name = dict.get('config_channel_name') or config_channel 87 config_channel_description = dict.get('description') or config_channel 88 89 row = rhnSQL.fetchone_dict(self._query_lookup_config_channel, 90 org_id=self.org_id, config_channel=config_channel) 91 if row: 92 raise rhnFault(4010, "Configuration channel %s already exists" % 93 config_channel, explain=0) 94 95 insert_call = rhnSQL.Function('rhn_config.insert_channel', 96 rhnSQL.types.NUMBER()) 97 config_channel_id = insert_call(self.org_id, 98 'normal', 99 config_channel_name, 100 config_channel, 101 config_channel_description) 102 103 rhnSQL.commit() 104 return {}
105 106 _query_config_channel_by_label = rhnSQL.Statement(""" 107 select id 108 from rhnConfigChannel 109 where org_id = :org_id 110 and label = :label 111 """) 112
113 - def management_remove_channel(self, dict):
114 log_debug(1) 115 self._get_and_validate_session(dict) 116 117 config_channel = dict.get('config_channel') 118 # XXX Validate the namespace 119 120 row = rhnSQL.fetchone_dict(self._query_config_channel_by_label, 121 org_id=self.org_id, label=config_channel) 122 123 if not row: 124 raise rhnFault(4009, "Channel not found") 125 126 delete_call = rhnSQL.Procedure('rhn_config.delete_channel') 127 128 try: 129 delete_call(row['id']) 130 except rhnSQL.SQLError: 131 e = sys.exc_info()[1] 132 errno = e.args[0] 133 if errno == 2292: 134 raise_with_tb(rhnFault(4005, "Cannot remove non-empty channel %s" % 135 config_channel, explain=0), sys.exc_info()[2]) 136 raise 137 138 log_debug(5, "Removed:", config_channel) 139 rhnSQL.commit() 140 return ""
141 142 _query_management_list_files = rhnSQL.Statement(""" 143 select cc.label config_channel, 144 cfn.path 145 from rhnConfigFileName cfn, 146 rhnConfigFileState cfs, 147 rhnConfigFile cf, 148 rhnConfigChannel cc 149 where cc.org_id = :org_id 150 and cc.label = :config_channel 151 and cc.id = cf.config_channel_id 152 and cf.state_id = cfs.id 153 and cfs.label = 'alive' 154 and cf.config_file_name_id = cfn.id 155 """) 156
157 - def management_list_files(self, dict):
158 log_debug(1) 159 self._get_and_validate_session(dict) 160 161 config_channel = dict.get('config_channel') 162 # XXX Validate the config channel 163 164 log_debug(3, "Org id", self.org_id, "Config channel", config_channel) 165 166 h = rhnSQL.prepare(self._query_management_list_files) 167 h.execute(org_id=self.org_id, config_channel=config_channel) 168 169 retval = [] 170 while 1: 171 row = h.fetchone_dict() 172 if not row: 173 break 174 val = {} 175 # Only copy a subset of the keys 176 for f in ['config_channel', 'path']: 177 val[f] = row[f] 178 179 retval.append(val) 180 log_debug(4, "pre sort", retval) 181 retval.sort(lambda x, y: cmp(x['path'], y['path'])) 182 log_debug(4, "Return value", retval) 183 return retval
184
185 - def management_get_file(self, dict):
186 log_debug(1) 187 self._get_and_validate_session(dict) 188 189 config_channel = dict.get('config_channel') 190 # XXX Validate the namespace 191 path = dict.get('path') 192 revision = dict.get('revision') 193 194 row = self._get_file(config_channel, path, revision=revision) 195 if not row: 196 raise rhnFault(4011, "File %s does not exist in channel %s" % 197 (path, config_channel), explain=0) 198 199 return self._format_file_results(row)
200 201 _query_list_file_revisions = rhnSQL.Statement(""" 202 select cr.revision 203 from rhnConfigChannel cc, 204 rhnConfigRevision cr, 205 rhnConfigFile cf 206 where cf.config_channel_id = cc.id 207 and cc.label = :config_channel 208 and cc.org_id = :org_id 209 and cf.config_file_name_id = lookup_config_filename(:path) 210 and cr.config_file_id = cf.id 211 order by revision desc 212 """) 213
214 - def management_list_file_revisions(self, dict):
215 log_debug(1) 216 self._get_and_validate_session(dict) 217 218 config_channel = dict.get('config_channel') 219 # XXX Validate the namespace 220 path = dict.get('path') 221 222 retval = [x['revision'] for x in rhnSQL.fetchall_dict(self._query_list_file_revisions, 223 org_id=self.org_id, config_channel=config_channel, path=path) or []] 224 if not retval: 225 raise rhnFault(4011, "File %s does not exist in channel %s" % 226 (path, config_channel), explain=0) 227 228 return retval
229
230 - def management_has_file(self, dict):
231 log_debug(1) 232 self._get_and_validate_session(dict) 233 234 config_channel = dict.get('config_channel') 235 # XXX Validate the namespace 236 path = dict.get('path') 237 row = self._get_file(config_channel, path) 238 if not row: 239 return {} 240 return { 241 'revision': row['revision'], 242 }
243 244 _query_get_file = """ 245 select :path path, 246 cc.label config_channel, 247 ccont.contents file_contents, 248 ccont.is_binary, 249 c.checksum_type, 250 c.checksum, 251 ccont.delim_start, ccont.delim_end, 252 cr.revision, 253 cf.modified, 254 ci.username, 255 ci.groupname, 256 ci.filemode, 257 cft.label, 258 ci.selinux_ctx, 259 case 260 when cft.label='symlink' then (select path from rhnConfigFileName where id = ci.SYMLINK_TARGET_FILENAME_ID) 261 else '' 262 end as symlink 263 from rhnConfigChannel cc, 264 rhnConfigInfo ci, 265 rhnConfigRevision cr 266 left join rhnConfigContent ccont 267 on cr.config_content_id = ccont.id 268 left join rhnChecksumView c 269 on ccont.checksum_id = c.id, 270 rhnConfigFile cf, 271 rhnConfigFileType cft 272 where cf.config_channel_id = cc.id 273 and cc.label = :config_channel 274 and cc.org_id = :org_id 275 and cf.config_file_name_id = lookup_config_filename(:path) 276 and cr.config_file_id = cf.id 277 and cr.config_info_id = ci.id 278 and cr.config_file_type_id = cft.id 279 """ 280 _query_get_file_latest = rhnSQL.Statement(_query_get_file + """ 281 and cf.latest_config_revision_id = cr.id 282 """) 283 _query_get_file_revision = rhnSQL.Statement(_query_get_file + """ 284 and cr.revision = :revision 285 """) 286
287 - def _get_file(self, config_channel, path, revision=None):
288 log_debug(2, config_channel, path) 289 params = { 290 'org_id': self.org_id, 291 'config_channel': config_channel, 292 'path': path, 293 } 294 if revision is None: 295 # Fetch the latest 296 q = self._query_get_file_latest 297 else: 298 params['revision'] = revision 299 q = self._query_get_file_revision 300 log_debug(4, params) 301 return rhnSQL.fetchone_dict(q, **params)
302 303 _query_lookup_config_file_by_channel = rhnSQL.Statement(""" 304 select cf.id, 305 cf.state_id 306 from rhnConfigFile cf, 307 rhnConfigChannel cc 308 where cc.org_id = :org_id 309 and cf.config_channel_id = cc.id 310 and cc.label = :config_channel 311 and cf.config_file_name_id = lookup_config_filename(:path) 312 """) 313
314 - def management_remove_file(self, dict):
315 log_debug(1) 316 self._get_and_validate_session(dict) 317 318 config_channel = dict.get('config_channel') 319 # XXX Validate the namespace 320 path = dict.get('path') 321 322 row = rhnSQL.fetchone_dict(self._query_lookup_config_file_by_channel, 323 org_id=self.org_id, config_channel=config_channel, path=path) 324 if not row: 325 raise rhnFault(4011, "File %s does not exist in channel %s" % 326 (path, config_channel), explain=0) 327 328 config_file_id = row['id'] 329 330 delete_call = rhnSQL.Procedure("rhn_config.delete_file") 331 delete_call(config_file_id) 332 333 rhnSQL.commit() 334 335 return {}
336 337 _query_update_file_state = rhnSQL.Statement(""" 338 update rhnConfigFile 339 set state_id = :state_id 340 where id = :config_file_id 341 """) 342
343 - def management_disable_file(self, dict):
344 log_debug(1) 345 self._get_and_validate_session(dict) 346 347 config_channel = dict.get('config_channel') 348 # XXX Validate the namespace 349 path = dict.get('path') 350 351 t = rhnSQL.Table('rhnConfigFileState', 'label') 352 state_id_dead = t['dead']['id'] 353 354 row = rhnSQL.fetchone_dict(self._query_lookup_config_file_by_channel, 355 config_channel=config_channel, path=path) 356 if not row or row['state_id'] == state_id_dead: 357 raise rhnFault(4011, "File %s does not exist in channel %s" % 358 (path, config_channel), explain=0) 359 360 rhnSQL.execute(self._query_update_file_state, 361 config_file_id=row['id'], state_id=state_id_dead) 362 rhnSQL.commit() 363 return {}
364
365 - def management_put_file(self, dict):
366 log_debug(1) 367 self._get_and_validate_session(dict) 368 369 config_channel = dict.get('config_channel') 370 row = self.lookup_org_config_channel_by_name(config_channel) 371 conf_channel_id = row['id'] 372 373 file_path = dict.get('path') 374 result = self.push_file(conf_channel_id, dict) 375 376 file_too_large = result.get('file_too_large') 377 if file_too_large: 378 raise rhnFault(4003, "File %s is too large (%s bytes)" % 379 (dict['path'], dict['size']), explain=0) 380 381 rhnSQL.commit() 382 return {}
383
384 - def management_get_delimiters(self, dict):
385 log_debug(1) 386 self._get_and_validate_session(dict) 387 388 return self._get_delimiters()
389
390 - def management_get_maximum_file_size(self, dict={}):
391 log_debug(1) 392 self._get_and_validate_session(dict) 393 394 return self._get_maximum_file_size()
395
396 - def __attributes_differ(self, fsrc, fdst):
397 """ Returns true if acl, ownership, type or selinux context differ. """ 398 return (fsrc['filemode'] != fdst['filemode']) or (fsrc['label'] != fdst['label']) or \ 399 (fsrc['username'] != fdst['username']) or (fsrc['groupname'] != fdst['groupname']) or \ 400 (fsrc['selinux_ctx'] != fdst['selinux_ctx'])
401
402 - def __header(self, path, fsrc, config_channel_src, fdst, config_channel_dst):
403 """ Returns diff like header for this two files. """ 404 template = "--- %s\t%s\tattributes: %s %s %s %s\tconfig channel: %s\trevision: %s" 405 first_row = template % (path, f_date(fsrc['modified']), ostr_to_sym(fsrc['filemode'], fsrc['label']), 406 fsrc['username'], fsrc['groupname'], fsrc['selinux_ctx'], config_channel_src, 407 fsrc['revision'], 408 ) 409 second_row = template % (path, f_date(fdst['modified']), ostr_to_sym(fdst['filemode'], fdst['label']), 410 fdst['username'], fdst['groupname'], fdst['selinux_ctx'], config_channel_dst, 411 fdst['revision'], 412 ) 413 return (first_row, second_row)
414
415 - def management_diff(self, dict):
416 log_debug(1) 417 self._get_and_validate_session(dict) 418 419 param_names = ['config_channel_src', 'revision_src', 'path', ] 420 for p in param_names: 421 val = dict.get(p) 422 if val is None: 423 raise rhnFault(4007, "No content sent for `%s'" % p) 424 425 log_debug(4, "Params sent", dict) 426 path = dict['path'] 427 428 config_channel_src = dict['config_channel_src'] 429 revision_src = dict.get('revision_src') 430 fsrc = self._get_file_revision(config_channel_src, revision_src, path) 431 432 config_channel_dst = dict.get('config_channel_dst') 433 if config_channel_dst is None: 434 config_channel_dst = config_channel_src 435 revision_dst = dict.get('revision_dst') 436 fdst = self._get_file_revision(config_channel_dst, revision_dst, path) 437 438 if fsrc['label'] != fdst['label']: 439 raise rhnFault(4017, 440 "Path %s is a %s in channel %s while it is a %s in channel %s" 441 % (path, fsrc['label'], 442 config_channel_src, fdst['label'], config_channel_dst), 443 explain=0) 444 445 if fsrc['label'] == 'symlink': 446 if (fsrc["symlink"] != fdst['symlink']) or self.__attributes_differ(fsrc, fdst): 447 (first_row, second_row) = self.__header(path, fsrc, config_channel_src, fdst, config_channel_dst) 448 first_row += ' target: %s' % fsrc["symlink"] 449 second_row += ' target: %s' % fdst["symlink"] 450 return first_row + "\n" + second_row + "\n" 451 return "" 452 453 diff = difflib.unified_diff( 454 fsrc['file_content'], fdst['file_content'], path, path, fsrc['modified'], fdst['modified'], lineterm='') 455 try: 456 first_row = next(diff) 457 except StopIteration: 458 return "" 459 460 if not first_row.startswith('---'): 461 # Hmm, weird 462 return first_row + '\n'.join(list(diff)) 463 464 try: 465 second_row = next(diff) 466 except StopIteration: 467 second_row = '' 468 469 if not second_row.startswith('+++'): 470 # Hmm, weird 471 return second_row + '\n'.join(list(diff)) 472 473 (first_row, second_row) = self.__header(path, fsrc, config_channel_src, fdst, config_channel_dst) 474 return first_row + "\n" + second_row + '\n' + '\n'.join(list(diff))
475
476 - def _get_file_revision(self, config_channel, revision, path):
477 if revision and not revision.isdigit(): 478 raise rhnFault(4016, "Invalid revision number '%s' specified for path %s " 479 "in channel %s" % (revision, path, config_channel), 480 explain=0) 481 482 f = self._get_file(config_channel, path, revision=revision) 483 if not f: 484 raise rhnFault(4011, "File %s (revision %s) does not exist " 485 "in channel %s" % (path, revision, config_channel), 486 explain=0) 487 if f['label'] == 'file' and f['is_binary'] == 'Y': 488 raise rhnFault(4004, "File %s (revision %s) seems to contain " 489 "binary data" % (path, revision), 490 explain=0) 491 492 # We have to read the contents of the first file here, because the LOB 493 # object is tied to a cursor; if we re-execute the cursor, the LOB 494 # seems to be invalid (bug 151220) 495 496 # Empty files or directories may have NULL instead of lobs 497 fc_lob = f.get('file_contents') 498 if fc_lob: 499 f['file_content'] = rhnSQL.read_lob(fc_lob).splitlines() 500 else: 501 f['file_content'] = '' 502 return f
503 504 # Helper functions 505 _query_org_config_channels = rhnSQL.Statement(""" 506 select cc.id, cc.label, cc.name, cct.label channel_type 507 from rhnConfigChannelType cct, rhnConfigChannel cc 508 where cc.label = :config_channel 509 and cc.org_id = :org_id 510 and cc.confchan_type_id = cct.id 511 """) 512
513 - def lookup_org_config_channel_by_name(self, config_channel):
514 row = rhnSQL.fetchone_dict(self._query_org_config_channels, 515 config_channel=config_channel, org_id=self.org_id) 516 if not row: 517 raise rhnFault(4009, "Configuration channel %s does not exist" % 518 config_channel, explain=0) 519 return row
520
521 - def _check_user_role(self):
522 user_roles = self.user.get_roles() 523 if 'config_admin' in user_roles or 'org_admin' in user_roles: 524 # All good 525 return 526 527 raise rhnFault(4006, 528 "User is not a allowed to manage config files")
529