#!/usr/bin/python -u """ Utility to upload or remove the packages from the packages repository. """ import logging, optparse, os, shutil, sys, tempfile import common from autotest_lib.client.common_lib import utils as client_utils from autotest_lib.client.common_lib import global_config, error from autotest_lib.client.common_lib import packages from autotest_lib.server import utils as server_utils c = global_config.global_config logging.basicConfig(level=logging.DEBUG) ACTION_REMOVE = 'remove' ACTION_UPLOAD = 'upload' ACTION_TAR_ONLY = 'tar_only' def get_exclude_string(client_dir): ''' Get the exclude string for the tar command to exclude specific subdirectories inside client_dir. For profilers we need to exclude everything except the __init__.py file so that the profilers can be imported. ''' exclude_string = ('--exclude="deps/*" --exclude="tests/*" ' '--exclude="site_tests/*" --exclude="**.pyc"') # Get the profilers directory prof_dir = os.path.join(client_dir, 'profilers') # Include the __init__.py file for the profilers and exclude all its # subdirectories for f in os.listdir(prof_dir): if os.path.isdir(os.path.join(prof_dir, f)): exclude_string += ' --exclude="profilers/%s"' % f # The '.' here is needed to zip the files in the current # directory. We use '-C' for tar to change to the required # directory i.e. src_dir and then zip up the files in that # directory(which is '.') excluding the ones in the exclude_dirs exclude_string += " ." # TODO(milleral): This is sad and ugly. http://crbug.com/258161 # Surprisingly, |exclude_string| actually means argument list, and # we'd like to package up the current global_config.ini also, so let's # just tack it on here. # Also note that this only works because tar prevents us from un-tarring # files into parent directories. exclude_string += " ../global_config.ini" return exclude_string def parse_args(): parser = optparse.OptionParser() parser.add_option("-d", "--dependency", help="package the dependency" " from client/deps directory and upload to the repo", dest="dep") parser.add_option("-p", "--profiler", help="package the profiler " "from client/profilers directory and upload to the repo", dest="prof") parser.add_option("-t", "--test", help="package the test from client/tests" " or client/site_tests and upload to the repo.", dest="test") parser.add_option("-c", "--client", help="package the client " "directory alone without the tests, deps and profilers", dest="client", action="store_true", default=False) parser.add_option("-f", "--file", help="simply uploads the specified" "file on to the repo", dest="file") parser.add_option("-r", "--repository", help="the URL of the packages" "repository location to upload the packages to.", dest="repo", default=None) parser.add_option("-o", "--output_dir", help="the output directory" "to place tarballs and md5sum files in.", dest="output_dir", default=None) parser.add_option("-a", "--action", help="the action to perform", dest="action", choices=(ACTION_UPLOAD, ACTION_REMOVE, ACTION_TAR_ONLY), default=None) parser.add_option("--all", help="Upload all the files locally " "to all the repos specified in global_config.ini. " "(includes the client, tests, deps and profilers)", dest="all", action="store_true", default=False) options, args = parser.parse_args() return options, args def get_build_dir(name, dest_dir, pkg_type): """Method to generate the build directory where the tarball and checksum is stored. The following package types are handled: test, dep, profiler. Package type 'client' is not handled. """ if pkg_type == 'client': # NOTE: The "tar_only" action for pkg_type "client" has no use # case yet. No known invocations of packager.py with # --action=tar_only send in clients in the command line. Please # confirm the behaviour is expected before this type is enabled for # "tar_only" actions. print ('Tar action not supported for pkg_type= %s, name = %s' % pkg_type, name) return None # For all packages, the work-dir should have 'client' appended to it. base_build_dir = os.path.join(dest_dir, 'client') if pkg_type == 'test': build_dir = os.path.join(get_test_dir(name, base_build_dir), name) else: # For profiler and dep, we append 's', and then append the name. # TODO(pmalani): Make this less fiddly? build_dir = os.path.join(base_build_dir, pkg_type + 's', name) return build_dir def process_packages(pkgmgr, pkg_type, pkg_names, src_dir, action, dest_dir=None): """Method to upload or remove package depending on the flag passed to it. If tar_only is set to True, this routine is solely used to generate a tarball and compute the md5sum from that tarball. If the tar_only flag is True, then the remove flag is ignored. """ exclude_string = ' .' names = [p.strip() for p in pkg_names.split(',')] for name in names: print "process_packages: Processing %s ... " % name if pkg_type == 'client': pkg_dir = src_dir exclude_string = get_exclude_string(pkg_dir) elif pkg_type == 'test': # if the package is a test then look whether it is in client/tests # or client/site_tests pkg_dir = os.path.join(get_test_dir(name, src_dir), name) else: # for the profilers and deps pkg_dir = os.path.join(src_dir, name) pkg_name = pkgmgr.get_tarball_name(name, pkg_type) exclude_string_tar = (( ' --exclude="**%s" --exclude="**%s.checksum" ' % (pkg_name, pkg_name)) + exclude_string) if action == ACTION_TAR_ONLY: # We don't want any pre-existing tarballs and checksums to # be repackaged, so we should purge these. build_dir = get_build_dir(name, dest_dir, pkg_type) try: packages.check_diskspace(build_dir) except error.RepoDiskFullError as e: msg = ("Work_dir directory for packages %s does not have " "enough space available: %s" % (build_dir, e)) raise error.RepoDiskFullError(msg) tarball_path = pkgmgr.tar_package(pkg_name, pkg_dir, build_dir, exclude_string_tar) # Create the md5 hash too. md5sum = pkgmgr.compute_checksum(tarball_path) md5sum_filepath = os.path.join(build_dir, pkg_name + '.checksum') with open(md5sum_filepath, "w") as f: f.write(md5sum) elif action == ACTION_UPLOAD: # Tar the source and upload temp_dir = tempfile.mkdtemp() try: try: packages.check_diskspace(temp_dir) except error.RepoDiskFullError, e: msg = ("Temporary directory for packages %s does not have " "enough space available: %s" % (temp_dir, e)) raise error.RepoDiskFullError(msg) # Check if tarball already exists. If it does, then don't # create a tarball again. tarball_path = os.path.join(pkg_dir, pkg_name); if os.path.exists(tarball_path): print("process_packages: Tarball %s already exists" % tarball_path) else: tarball_path = pkgmgr.tar_package(pkg_name, pkg_dir, temp_dir, exclude_string_tar) # Compare the checksum with what packages.checksum has. If they # match then we don't need to perform the upload. if not pkgmgr.compare_checksum(tarball_path): pkgmgr.upload_pkg(tarball_path, update_checksum=True) else: logging.warning('Checksum not changed for %s, not copied ' 'in packages/ directory.', tarball_path) finally: # remove the temporary directory shutil.rmtree(temp_dir) elif action == ACTION_REMOVE: pkgmgr.remove_pkg(pkg_name, remove_checksum=True) print "Done." def tar_packages(pkgmgr, pkg_type, pkg_names, src_dir, temp_dir): """Tar all packages up and return a list of each tar created""" tarballs = [] exclude_string = ' .' names = [p.strip() for p in pkg_names.split(',')] for name in names: print "tar_packages: Processing %s ... " % name if pkg_type == 'client': pkg_dir = src_dir exclude_string = get_exclude_string(pkg_dir) elif pkg_type == 'test': # if the package is a test then look whether it is in client/tests # or client/site_tests pkg_dir = os.path.join(get_test_dir(name, src_dir), name) else: # for the profilers and deps pkg_dir = os.path.join(src_dir, name) pkg_name = pkgmgr.get_tarball_name(name, pkg_type) # We don't want any pre-existing tarballs and checksums to # be repackaged, so we should purge these. exclude_string_tar = (( ' --exclude="**%s" --exclude="**%s.checksum" ' % (pkg_name, pkg_name)) + exclude_string) # Check if tarball already exists. If it does, don't duplicate # the effort. tarball_path = os.path.join(pkg_dir, pkg_name); if os.path.exists(tarball_path): print("tar_packages: Tarball %s already exists" % tarball_path); else: tarball_path = pkgmgr.tar_package(pkg_name, pkg_dir, temp_dir, exclude_string_tar) tarballs.append(tarball_path) return tarballs def process_all_packages(pkgmgr, client_dir, action): """Process a full upload of packages as a directory upload.""" dep_dir = os.path.join(client_dir, "deps") prof_dir = os.path.join(client_dir, "profilers") # Directory where all are kept temp_dir = tempfile.mkdtemp() try: packages.check_diskspace(temp_dir) except error.RepoDiskFullError, e: print ("Temp destination for packages is full %s, aborting upload: %s" % (temp_dir, e)) os.rmdir(temp_dir) sys.exit(1) # process tests tests_list = get_subdir_list('tests', client_dir) tests = ','.join(tests_list) # process site_tests site_tests_list = get_subdir_list('site_tests', client_dir) site_tests = ','.join(site_tests_list) # process deps deps_list = get_subdir_list('deps', client_dir) deps = ','.join(deps_list) # process profilers profilers_list = get_subdir_list('profilers', client_dir) profilers = ','.join(profilers_list) # Update md5sum if action == ACTION_UPLOAD: all_packages = [] all_packages.extend(tar_packages(pkgmgr, 'profiler', profilers, prof_dir, temp_dir)) all_packages.extend(tar_packages(pkgmgr, 'dep', deps, dep_dir, temp_dir)) all_packages.extend(tar_packages(pkgmgr, 'test', site_tests, client_dir, temp_dir)) all_packages.extend(tar_packages(pkgmgr, 'test', tests, client_dir, temp_dir)) all_packages.extend(tar_packages(pkgmgr, 'client', 'autotest', client_dir, temp_dir)) for package in all_packages: pkgmgr.upload_pkg(package, update_checksum=True) client_utils.run('rm -rf ' + temp_dir) elif action == ACTION_REMOVE: process_packages(pkgmgr, 'test', tests, client_dir, action=action) process_packages(pkgmgr, 'test', site_tests, client_dir, action=action) process_packages(pkgmgr, 'client', 'autotest', client_dir, action=action) process_packages(pkgmgr, 'dep', deps, dep_dir, action=action) process_packages(pkgmgr, 'profiler', profilers, prof_dir, action=action) # Get the list of sub directories present in a directory def get_subdir_list(name, client_dir): dir_name = os.path.join(client_dir, name) return [f for f in os.listdir(dir_name) if os.path.isdir(os.path.join(dir_name, f)) ] # Look whether the test is present in client/tests and client/site_tests dirs def get_test_dir(name, client_dir): names_test = os.listdir(os.path.join(client_dir, 'tests')) names_site_test = os.listdir(os.path.join(client_dir, 'site_tests')) if name in names_test: src_dir = os.path.join(client_dir, 'tests') elif name in names_site_test: src_dir = os.path.join(client_dir, 'site_tests') else: print "Test %s not found" % name sys.exit(0) return src_dir def main(): # get options and args options, args = parse_args() server_dir = server_utils.get_server_dir() autotest_dir = os.path.abspath(os.path.join(server_dir, '..')) # extract the pkg locations from global config repo_urls = c.get_config_value('PACKAGES', 'fetch_location', type=list, default=[]) upload_paths = c.get_config_value('PACKAGES', 'upload_location', type=list, default=[]) if options.repo: upload_paths.append(options.repo) # Having no upload paths basically means you're not using packaging. if not upload_paths: print("No upload locations found. Please set upload_location under" " PACKAGES in the global_config.ini or provide a location using" " the --repository option.") return client_dir = os.path.join(autotest_dir, "client") # Bail out if the client directory does not exist if not os.path.exists(client_dir): sys.exit(0) dep_dir = os.path.join(client_dir, "deps") prof_dir = os.path.join(client_dir, "profilers") # Due to the delayed uprev-ing of certain ebuilds, we need to support # both the legacy command line and the new one. # So, if the new "action" option isn't specified, try looking for the # old style remove/upload argument if options.action is None: if len(args) == 0 or args[0] not in ['upload', 'remove']: print("Either 'upload' or 'remove' needs to be specified " "for the package") sys.exit(0) cur_action = args[0] else: cur_action = options.action if cur_action == ACTION_TAR_ONLY and options.output_dir is None: print("An output dir has to be specified") sys.exit(0) pkgmgr = packages.PackageManager(autotest_dir, repo_urls=repo_urls, upload_paths=upload_paths, run_function_dargs={'timeout':600}) if options.all: process_all_packages(pkgmgr, client_dir, action=cur_action) if options.client: process_packages(pkgmgr, 'client', 'autotest', client_dir, action=cur_action) if options.dep: process_packages(pkgmgr, 'dep', options.dep, dep_dir, action=cur_action, dest_dir=options.output_dir) if options.test: process_packages(pkgmgr, 'test', options.test, client_dir, action=cur_action, dest_dir=options.output_dir) if options.prof: process_packages(pkgmgr, 'profiler', options.prof, prof_dir, action=cur_action, dest_dir=options.output_dir) if options.file: if cur_action == ACTION_REMOVE: pkgmgr.remove_pkg(options.file, remove_checksum=True) elif cur_action == ACTION_UPLOAD: pkgmgr.upload_pkg(options.file, update_checksum=True) if __name__ == "__main__": main()