#!/usr/bin/python """ Copyright 2015, Kolja von Sawilski This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. """ import datetime import time import xmlrpclib import os import sys import getpass import re from commands import getoutput from pprint import pprint from optparse import OptionParser, OptionGroup default_server = 'satellite.example.com' parser = OptionParser(description = 'Clone errata(s) or CVE(s) to channel(s)') parser.add_option("-e", dest="errata", help="Comma separated list of errata or CVE to clone.", metavar="{errata|cve}[,{errata|cve}[,...]]") parser.add_option("-c", dest="channel", help="Comma separated list of channels to clone to.\ Errata is only cloned if it is already applied to the\ channel clone source! If not set, clone to all channels.", metavar="channel[,channel[,...]]") parser.add_option("-q", dest="query", help="Regular expression for channel selection.\ Do not use with options -c, -t and -r.", metavar="re") parser.add_option("-t", action="store_true", dest="tree", help="Get channel parent for given channels and apply errata to\ whole tree. You probably want to use this always", metavar="tree") parser.add_option("-r", action="store_true", dest="recur", help="Recursive apply to all clone destinations", metavar="recur") parser.add_option("-u", dest="user", help="Satellite user account", metavar="user") parser.add_option("-s", dest="server", help="Satellite server name", metavar="server") parser.add_option("-y", action="store_true", dest="yes", help="Clone without confirmation", metavar="YES") example1_opts = OptionGroup( parser, 'Example 1: Clone errata RHSA-2015:1981 to the parent of \n\ and all childs', 'clone-errata.py -e RHSA-2015:1981 -c -t', ) parser.add_option_group(example1_opts) example2_opts = OptionGroup( parser, 'Example 2: Clone errata RHSA-2015:2068 to all applicable channels', 'clone-errata.py -e RHSA-2015:2068 -r -t', ) parser.add_option_group(example2_opts) example3_opts = OptionGroup( parser, 'Example 3: Clone errata RHSA-2015:2068 to all channels with "_dev_" \n\ in their name', 'clone-errata.py -e RHSA-2015:2068 -q _dev_', ) parser.add_option_group(example3_opts) example4_opts = OptionGroup( parser, 'Example 4: Clone errata for CVE-2015-7181 to all channels with \'-server.*-7$\' \n\ in their name', 'clone-errata.py -e CVE-2015-7181 -q \'-server.*-7$\'', ) parser.add_option_group(example4_opts) (options, args) = parser.parse_args() if not options.errata: print print "Error: Need at least option \'-e {errata|cve}[,{errata|cve}[,...]]\'" print parser.print_help() sys.exit(1) errata_list = options.errata channel_list = options.channel regexp = options.query no_ask = options.yes use_tree = options.tree use_recur = options.recur if not options.server: SATELLITE_URL = "https://" + default_server + "/rpc/api" else: SATELLITE_URL = "https://" + options.server + "/rpc/api" if not options.user: SATELLITE_LOGIN = getoutput("logname") else: SATELLITE_LOGIN = options.user SATELLITE_PASSWORD = getpass.getpass("Satellite password for " + SATELLITE_LOGIN + ": ") client = xmlrpclib.Server(SATELLITE_URL, verbose=0) key = client.auth.login(SATELLITE_LOGIN, SATELLITE_PASSWORD) def build_channel_list(): all_channels_dict = {} all_channels = client.channel.listAllChannels(key) # get clone_original for all channels for channel in all_channels: channels_details = client.channel.software.getDetails(key, channel.get('id')) clone_original = channels_details.get('clone_original') parent_channel = channels_details.get('parent_channel_label') all_channels_dict[channel.get('label')] = { 'clone_original': clone_original, 'parent_channel': parent_channel, 'children': [] , 'clone_destinations': [], 'clone_level' : -1 } # get clone level, level 0 for channel in all_channels_dict: if all_channels_dict[channel]['clone_original'] == '': all_channels_dict[channel]['clone_level'] = 0 all_channels_dict[channel]['clone_original_root'] = channel # add children parent = all_channels_dict[channel]['parent_channel'] if not parent == '': all_channels_dict[parent]['children'].append(channel) change_done = True level = 1 while change_done: change_done = False for channel in all_channels_dict: if all_channels_dict[channel]['clone_level'] == level - 1: channel_label = channel clone_original_root = \ all_channels_dict[channel]['clone_original_root'] for channel2 in all_channels_dict: if all_channels_dict[channel2]['clone_original'] == \ channel_label: change_done = True all_channels_dict[channel2]['clone_level'] = level all_channels_dict[channel2]['clone_original_root'] = \ clone_original_root # add children parent = all_channels_dict[channel2]['parent_channel'] if not parent == '': all_channels_dict[parent]['children']\ .append(channel2) # add clone_destinations clone_original = \ all_channels_dict[channel2]['clone_original'] if not clone_original == '': all_channels_dict[clone_original]\ ['clone_destinations'].append(channel2) level = level + 1 return all_channels_dict def compare_errata(errata1,errata2): # no idea if errata are the same, # compare last 9 digits of name, synopsis and issue_date errata1_nr = errata1[-9:] errata2_nr = errata2[-9:] if errata1_nr != errata2_nr: return False errata1_detail = client.errata.getDetails(key,errata1) errata2_detail = client.errata.getDetails(key,errata2) errata1_synopsis = errata1_detail.get('synopsys') errata2_synopsis = errata2_detail.get('synopsys') if errata1_synopsis != errata2_synopsis: return False errata1_issue_date = errata1_detail.get('issue_date') errata2_issue_date = errata2_detail.get('issue_date') if errata1_issue_date != errata2_issue_date: return False return True def cve2errata(cve): erratas = [] for errata in client.errata.findByCve(key,cve): advisory_name = errata.get('advisory_name') # we only care about red hat erratas if advisory_name.startswith('RH'): erratas.append(advisory_name) return erratas def find_org_errata(errata): errata_suf = errata[-12:] errata_exp = 'RH' + errata_suf if compare_errata(errata,errata_exp): return errata_exp return 'None' def build_errata_array(errata_list): errata_array = [] tmp_arr = errata_list.split(',') for erra in tmp_arr: if erra.startswith('CVE'): errata_array = errata_array + cve2errata(erra) if erra.startswith('CL'): errata_array.append(find_org_errata(erra)) if erra.startswith('RH'): errata_array.append(erra) # no duplicates errata_set = set(errata_array) errata_array = list(errata_set) return errata_array def build_channel_array(channel_list): channel_array = channel_list.split(',') return channel_array def build_channel_array_all(): channel_array = [] for channel in all_channels_dict: if all_channels_dict[channel]['clone_level'] != 0: channel_array.append(channel) return channel_array def get_clone_original(channel): return all_channels_dict[channel]['clone_original'] def get_clone_root(channel): return all_channels_dict[channel]['clone_original_root'] def get_tree(channel): tree = [ channel ] parent = all_channels_dict[channel]['parent_channel'] # if parent is empty then channel is already parent if parent == '': parent = channel tree = tree + all_channels_dict[parent]['children'] return tree def build_tree_array(channel_list): tree_array = [] channel_array = channel_list.split(',') for channel in channel_array: tree_array = tree_array + get_tree(channel) return tree_array def build_recur_array_recur(channel_list): recur_array = [] channel_array = channel_list.split(',') for channel in channel_array: clone_destinations = all_channels_dict[channel]['clone_destinations'] if clone_destinations != []: recur_array = recur_array + clone_destinations # do the recursion recur_array = recur_array + \ build_recur_array_recur(','.join(clone_destinations)) return recur_array def build_recur_array(channel_list): recur_array = channel_list.split(',') recur_array = recur_array + build_recur_array_recur(channel_list) return recur_array def build_channel_array_regexp(regexp): regexp_array = [] for channel in all_channels_dict: match = re.search( regexp, channel) if match: regexp_array.append(channel) return regexp_array def errata_in_channel(errata,channel): channel_errata = client.channel.software.listErrata(key,channel) for ch_errata in channel_errata: if compare_errata(ch_errata.get('advisory_name'),errata): return True return False def apply_errata(errata,channel): print 'Apply ' + errata + ' to ' + channel if errata_in_channel(errata,channel): print " Already in channel" return clone_root = get_clone_root(channel) print ' Clone root: ' + clone_root if not errata_in_channel(errata,clone_root): print " Errata not applicable" return clone_original = get_clone_original(channel) print ' Clone original: ' + clone_original if not errata_in_channel(errata,clone_original): print " Errata not found in clone original" return print " Ready to apply" cloneAsOriginal(channel,errata) def cloneAsOriginal(channel,advname): if no_ask != True: ans = raw_input('Clone errata? [N/y]: ') if ans != 'y': return result = client.errata.cloneAsOriginal(key,channel,[advname]) print 'Cloned as ' + result[0].get('advisory_name') + ' with id ' + \ str(result[0].get('id')) def get_clone_level(channel): return all_channels_dict[channel]['clone_level'] def order_channel_array(channel_array): return sorted(channel_array, key=get_clone_level) ############################# print "Building channel list ..." all_channels_dict = build_channel_list() print "Done." errata_array = build_errata_array(errata_list) if channel_list != None: if use_tree: channel_array = build_tree_array(channel_list) channel_list = ','.join(channel_array) if use_recur: channel_array = build_recur_array(channel_list) if not use_tree and not use_recur: channel_array = build_channel_array(channel_list) else: if regexp == None: print "Build channel destination list for all channels" channel_array = build_channel_array_all() else: channel_array = build_channel_array_regexp(regexp) # sort array by clone_level to get ordered cloning channel_array = (order_channel_array(channel_array)) for errata in errata_array: for channel in channel_array: print apply_errata(errata,channel) print ' ---------------------'