Package proxy :: Package pm :: Module rhn_package_manager
[hide private]
[frames] | no frames]

Source Code for Module proxy.pm.rhn_package_manager

  1  #!/usr/bin/python2 
  2  # 
  3  # Copyright (c) 2008--2020 Red Hat, Inc. 
  4  # 
  5  # This software is licensed to you under the GNU General Public License, 
  6  # version 2 (GPLv2). There is NO WARRANTY for this software, express or 
  7  # implied, including the implied warranties of MERCHANTABILITY or FITNESS 
  8  # FOR A PARTICULAR PURPOSE. You should have received a copy of GPLv2 
  9  # along with this software; if not, see 
 10  # http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. 
 11  # 
 12  # Red Hat trademarks are not licensed under GPLv2. No permission is 
 13  # granted to use or replicate Red Hat trademarks that are incorporated 
 14  # in this software or its documentation. 
 15  # 
 16  # Authors: Mihai Ibanescu <misa@redhat.com> 
 17  #          Todd Warner <taw@redhat.com> 
 18  # 
 19  """\ 
 20  Management tool for the Spacewalk Proxy. 
 21   
 22  This script performs various management operations on the Spacewalk Proxy: 
 23  - Creates the local directory structure needed to store local packages 
 24  - Uploads packages from a given directory to the RHN servers 
 25  - Optionally, once the packages are uploaded, they can be linked to (one or 
 26    more) channels, and copied in the local directories for these channels. 
 27  - Lists the RHN server's vision on a certain channel 
 28  - Checks if the local image of the channel (the local directory) is in sync 
 29    with the server's image, and prints the missing packages (or the extra 
 30    ones) 
 31  - Cache any RPM content locally to avoid needing to download them. This can be 
 32    particularly useful if bandwitdth is precious or the connection to the server 
 33    is slow. 
 34  """ 
 35   
 36  # system imports 
 37  import gzip 
 38  import os 
 39  from xml.dom import minidom 
 40  import sys 
 41  import shutil 
 42  import xmlrpclib 
 43  from optparse import Option, OptionParser 
 44   
 45  # RHN imports 
 46  from spacewalk.common.rhnConfig import CFG, initCFG 
 47  from spacewalk.common.rhnLib import parseUrl 
 48  initCFG('proxy.package_manager') 
 49  # pylint: disable=E0012, C0413 
 50  from rhnpush.uploadLib import UploadError 
 51  from rhnpush import uploadLib 
 52  from proxy.broker.rhnRepository import computePackagePaths 
 53   
 54  # globals 
 55  PREFIX = 'rhn' 
 56   
 57   
58 -def main():
59 # Initialize a command-line processing object with a table of options 60 optionsTable = [ 61 Option('-v', '--verbose', action='count', help='Increase verbosity'), 62 Option('-d', '--dir', action='store', help='Process packages from this directory'), 63 Option('-L', '--cache-locally', action='store_true', 64 help='Locally cache packages so that Proxy will not ever need to ' 65 + 'download them. Changes nothing on the upstream server.'), 66 Option('-e', '--from-export', action='store', dest='export_location', 67 help='Process packages from this channel export. Can only be used ' 68 + 'with --cache-locally or --copyonly.'), 69 Option('-c', '--channel', action='append', 70 help='Channel to operate on. When used with --from-export ' 71 + 'specifies channels to cache rpms for, else specifies channels ' 72 + 'that we will be pushing into.'), 73 Option('-n', '--count', action='store', help='Process this number of headers per call', type='int'), 74 Option('-l', '--list', action='store_true', help='Only list the specified channels'), 75 Option('-s', '--sync', action='store_true', help='Check if in sync with the server'), 76 Option('-p', '--printconf', action='store_true', help='Print the configuration and exit'), 77 Option('-X', '--exclude', action="append", help="Exclude packages that match this glob expression"), 78 Option('--newest', action='store_true', help='Only push the files that are newer than the server ones'), 79 Option('--stdin', action='store_true', help='Read the package names from stdin'), 80 Option('--nosig', action='store_true', help="Push unsigned packages"), 81 Option('--username', action='store', help='Use this username to connect to RHN'), 82 Option('--password', action='store', help='Use this password to connect to RHN'), 83 Option('--source', action='store_true', help='Upload source package headers'), 84 Option('--dontcopy', action='store_true', help='Do not copy packages to the local directory'), 85 Option('--copyonly', action='store_true', 86 help="Only copy packages; don't reimport. Same as --cache-locally"), 87 Option('--test', action='store_true', help='Only print the packages to be pushed'), 88 Option('-N', '--new-cache', action='store_true', help='Create a new username/password cache'), 89 Option('--no-ssl', action='store_true', help='Turn off SSL (not recommended).'), 90 Option('--no-session-caching', action='store_true', 91 help='Disables session-token authentication.'), 92 Option('-?', '--usage', action='store_true', help="Briefly describe the options"), 93 ] 94 # Process the command line arguments 95 optionParser = OptionParser(option_list=optionsTable, usage="USAGE: %prog [OPTION] [<package>]") 96 options, files = optionParser.parse_args() 97 upload = UploadClass(options, files=files) 98 99 if options.usage: 100 optionParser.print_usage() 101 sys.exit(0) 102 103 if options.printconf: 104 CFG.show() 105 return 106 107 if options.list: 108 upload.list() 109 return 110 111 if options.sync: 112 upload.checkSync() 113 return 114 115 # It's just an alias to copyonly 116 if options.cache_locally: 117 options.copyonly = True 118 119 # remeber to process dir option before export, export can overwrite dir 120 if options.dir: 121 upload.directory() 122 if options.export_location: 123 if not options.copyonly: 124 upload.die(0, "--from-export can only be used with --cache-locally" 125 + " or --copyonly") 126 if options.source: 127 upload.die(0, "--from-export cannot be used with --source") 128 upload.from_export() 129 if options.stdin: 130 upload.readStdin() 131 132 # if we're going to allow the user to specify packages by dir *and* export 133 # *and* stdin *and* package list (why not?) then we have to uniquify 134 # the list afterwards. Sort just for user-friendly display. 135 upload.files = sorted(list(set(upload.files))) 136 137 if options.copyonly: 138 if not upload.files: 139 upload.die(0, "Nothing to do; exiting. Try --help") 140 if options.test: 141 upload.test() 142 return 143 upload.copyonly() 144 return 145 146 if options.exclude: 147 upload.filter_excludes() 148 149 if options.newest: 150 upload.newest() 151 152 if not upload.files: 153 upload.die(0, "Nothing to do; exiting. Try --help") 154 155 if options.test: 156 upload.test() 157 return 158 159 try: 160 upload.uploadHeaders() 161 except UploadError, e: 162 sys.stderr.write("Upload error: %s\n" % e)
163 164
165 -class UploadClass(uploadLib.UploadClass):
166 # pylint: disable=R0904,W0221 167
168 - def setURL(self, path='/APP'):
169 # overloaded for uploadlib.py 170 if not CFG.RHN_PARENT: 171 self.die(-1, "rhn_parent not set in the configuration file") 172 self.url = CFG.RHN_PARENT 173 scheme = 'http://' 174 if not self.options.no_ssl and CFG.USE_SSL: 175 # i.e., --no-ssl overrides the USE_SSL config variable. 176 scheme = 'https://' 177 self.url = CFG.RHN_PARENT or '' 178 self.url = parseUrl(self.url)[1].split(':')[0] 179 self.url = scheme + self.url + path
180 181 # The rpm names in channel exports have been changed to be something like 182 # rhn-package-XXXXXX.rpm, but that's okay because the rpm headers are 183 # still intact and that's what we use to determine the destination 184 # filename. Read the channel xml to determin what rpms to cache if the 185 # --channel option was used.
186 - def from_export(self):
187 export_dir = self.options.export_location 188 self.warn(1, "Getting files from channel export: ", export_dir) 189 if not self.options.channel: 190 self.warn(2, "No channels specified, getting all files") 191 # If no channels specified just upload all rpms from 192 # all the rpm directories 193 for hash_dir in uploadLib.listdir(os.path.join( 194 export_dir, "rpms")): 195 self.options.dir = hash_dir 196 self.directory() 197 return 198 # else... 199 self.warn(2, "Getting only files in these channels", 200 self.options.channel) 201 # Read the channel xml and add only packages that are in these channels 202 package_set = set([]) 203 for channel in self.options.channel: 204 xml_path = os.path.join(export_dir, "channels", channel, 205 "channel.xml.gz") 206 if not os.access(xml_path, os.R_OK): 207 self.warn(0, "Could not find metadata for channel %s, skipping..." % channel) 208 print "Could not find metadata for channel %s, skipping..." % channel 209 continue 210 dom = minidom.parse(gzip.open(xml_path)) 211 # will only ever be the one 212 dom_channel = dom.getElementsByTagName('rhn-channel')[0] 213 package_set.update(dom_channel.attributes['packages'] 214 .value.encode('ascii', 'ignore').split()) 215 # Try to find relevent packages in the export 216 for hash_dir in uploadLib.listdir(os.path.join(export_dir, "rpms")): 217 for rpm in uploadLib.listdir(hash_dir): 218 # rpm name minus '.rpm' 219 if os.path.basename(rpm)[:-4] in package_set: 220 self.files.append(rpm)
221
222 - def setServer(self):
223 try: 224 uploadLib.UploadClass.setServer(self) 225 uploadLib.call(self.server.packages.no_op, raise_protocol_error=True) 226 except xmlrpclib.ProtocolError, e: 227 if e.errcode == 404: 228 self.use_session = False 229 self.setURL('/XP') 230 uploadLib.UploadClass.setServer(self) 231 else: 232 raise
233
234 - def authenticate(self):
235 if self.use_session: 236 uploadLib.UploadClass.authenticate(self) 237 else: 238 self.setUsernamePassword()
239
240 - def setProxyUsernamePassword(self):
241 # overloaded for uploadlib.py 242 self.proxyUsername = CFG.HTTP_PROXY_USERNAME 243 self.proxyPassword = CFG.HTTP_PROXY_PASSWORD
244
245 - def setProxy(self):
246 # overloaded for uploadlib.py 247 self.proxy = CFG.HTTP_PROXY
248
249 - def setCAchain(self):
250 # overloaded for uploadlib.py 251 self.ca_chain = CFG.CA_CHAIN
252
253 - def setNoChannels(self):
254 self.channels = self.options.channel
255
256 - def checkSync(self):
257 # set the org 258 self.setOrg() 259 # set the URL 260 self.setURL() 261 # set the channels 262 self.setChannels() 263 # set the server 264 self.setServer() 265 266 self.authenticate() 267 268 # List the channel's contents 269 channel_list = self._listChannel() 270 271 # Convert it to a hash of hashes 272 remotePackages = {} 273 for channel in self.channels: 274 remotePackages[channel] = {} 275 for p in channel_list: 276 channelName = p[-1] 277 key = tuple(p[:5]) 278 remotePackages[channelName][key] = None 279 280 missing = [] 281 for package in channel_list: 282 found = False 283 # if the package includes checksum info 284 if self.use_checksum_paths: 285 checksum = package[6] 286 else: 287 checksum = None 288 289 packagePaths = computePackagePaths(package, 0, PREFIX, checksum) 290 for packagePath in packagePaths: 291 packagePath = "%s/%s" % (CFG.PKG_DIR, packagePath) 292 if os.path.isfile(packagePath): 293 found = True 294 break 295 if not found: 296 missing.append([package, packagePaths[0]]) 297 298 if not missing: 299 self.warn(0, "Channels in sync with the server") 300 return 301 302 for package, packagePath in missing: 303 channelName = package[-1] 304 self.warn(0, "Missing: %s in channel %s (path %s)" % ( 305 rpmPackageName(package), channelName, packagePath))
306
307 - def processPackage(self, package, filename, checksum=None):
308 if self.options.dontcopy: 309 return 310 311 if not CFG.PKG_DIR: 312 self.warn(1, "No package directory specified; will not copy the package") 313 return 314 315 if not self.use_checksum_paths: 316 checksum = None 317 # Copy file to the prefered path 318 packagePath = computePackagePaths(package, self.options.source, 319 PREFIX, checksum)[0] 320 packagePath = "%s/%s" % (CFG.PKG_DIR, packagePath) 321 destdir = os.path.dirname(packagePath) 322 if not os.path.isdir(destdir): 323 # Try to create it 324 try: 325 os.makedirs(destdir, 0755) 326 except OSError: 327 self.warn(0, "Could not create directory %s" % destdir) 328 return 329 self.warn(1, "Copying %s to %s" % (filename, packagePath)) 330 shutil.copy2(filename, packagePath) 331 # Make sure the file permissions are set correctly, so that Apache can 332 # see the files 333 os.chmod(packagePath, 0644)
334
335 - def _listChannelSource(self):
336 self.die(1, "Listing source rpms not supported")
337
338 - def copyonly(self):
339 # Set the forcing factor 340 self.setForce() 341 # Relative directory 342 self.setRelativeDir() 343 # Set the count 344 self.setCount() 345 346 if not CFG.PKG_DIR: 347 self.warn(1, "No package directory specified; will not copy the package") 348 return 349 350 # Safe because proxy X can't be activated against Spacewalk / Satellite 351 # < X. 352 self.use_checksum_paths = True 353 354 for filename in self.files: 355 fileinfo = self._processFile(filename, 356 relativeDir=self.relativeDir, 357 source=self.options.source, 358 nosig=self.options.nosig) 359 self.processPackage(fileinfo['nvrea'], filename, 360 fileinfo['checksum'])
361 362
363 -def rpmPackageName(p):
364 return "%s-%s-%s.%s.rpm" % (p[0], p[1], p[2], p[4])
365 366 if __name__ == '__main__': 367 try: 368 main() 369 except SystemExit, se: 370 sys.exit(se.code) 371