#!/usr/bin/env python2 ''' Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ''' import re import os import sys import logging import subprocess from optparse import OptionParser import ConfigParser USAGE = "Usage: %prog [OPTION]... URL" DESCRIPTION = "URL should point to full tar.gz location e.g.: https://public-repo-1.hortonworks.com/something/ambari-server.tar.gz" logger = logging.getLogger("install_ambari_tarball") PREINST_SCRIPT = "preinst" PRERM_SCRIPT = "prerm" POSTINST_SCRIPT = "postinst" POSTRM_SCRIPT = "postrm" OS_CHECK = "os_check.py" OS_PACKAGE_DEPENDENCIES = "dependencies.properties" OS_FAMILY_DESCRIPTION = "resources/os_family.json" RPM_DEPENDENCIES_PROPERTY = "rpm.dependency.list" DEB_DEPENDENCIES_PROPERTY = "deb.dependency.list" FILES_TO_DOWNLOAD = [PREINST_SCRIPT, PRERM_SCRIPT, POSTINST_SCRIPT, POSTRM_SCRIPT, OS_CHECK, OS_FAMILY_DESCRIPTION, OS_PACKAGE_DEPENDENCIES] ROOT_FOLDER_ENV_VARIABLE = "RPM_INSTALL_PREFIX" class Utils: verbose = False @staticmethod def os_call(command, logoutput=None, env={}): shell = not isinstance(command, list) print_output = logoutput==True or (logoutput==None and Utils.verbose) if not print_output: stdout = subprocess.PIPE stderr = subprocess.STDOUT else: stdout = stderr = None logger.info("Running '{0}'".format(command)) proc = subprocess.Popen(command, shell=shell, stdout=stdout, stderr=stderr, env=env) if not print_output: out = proc.communicate()[0].strip('\n') else: proc.wait() out = None code = proc.returncode if code: err_msg = ("Execution of '%s'\n returned %d. %s") % (command, code, out) raise OsCallFailure(err_msg) return out @staticmethod def install_package(name): from os_check import OSCheck logger.info("Checking for existance of {0} dependency package".format(name)) is_rpm = not OSCheck.is_ubuntu_family() if is_rpm: is_installed_cmd = ['rpm', '-q'] + [name] install_cmd = ['sudo', 'yum', '-y', 'install'] + [name] else: is_installed_cmd = ['dpkg', '-s'] + [name] install_cmd = ['sudo', 'apt-get', '-y', 'install'] + [name] try: Utils.os_call(is_installed_cmd, logoutput=False) logger.info("Package {0} is already installed. Skipping installation.".format(name)) except OsCallFailure: logger.info("Package {0} is not installed. Installing it...".format(name)) Utils.os_call(install_cmd) class FakePropertiesHeader(object): """ Hacky class to parse properties file without sections. see http://stackoverflow.com/questions/2819696/module-to-use-when-parsing-properties-file-in-python/2819788#2819788 """ FAKE_SECTION_NAME = 'section' def __init__(self, fp): self.fp = fp self.sechead = '[{0}]\n'.format(FakePropertiesHeader.FAKE_SECTION_NAME) def readline(self): if self.sechead: try: return self.sechead finally: self.sechead = None else: return self.fp.readline() class OsCallFailure(RuntimeError): pass class Installer: def __init__(self, archive_url, root_folder, verbose, skip_dependencies): splited_url = archive_url.split('/') self.archive_name = splited_url[-1] self.base_url = '/'.join(splited_url[0:-1]) self.root_folder = root_folder self.verbose = verbose self.skip_dependencies = skip_dependencies def download_files(self, files_list): for name in files_list: dirname = os.path.dirname(name) if dirname: Utils.os_call(["mkdir", "-p", dirname]) url = "{0}/{1}".format(self.base_url, name) logger.info("Downloading {0}".format(url)) Utils.os_call(["wget", "-O", name, url]) def run(self): self.download_files([self.archive_name] +FILES_TO_DOWNLOAD) # [self.archive_name] + self.check_dependencies() self.run_script(PRERM_SCRIPT, ["remove"]) # in case we are upgrading self.run_script(POSTRM_SCRIPT, ["remove"]) # in case we are upgrading self.run_script(PREINST_SCRIPT, ["install"]) self.extract_archive() self.run_script(POSTINST_SCRIPT, ["configure"]) def check_dependencies(self): from os_check import OSCheck os_family = OSCheck.get_os_family() os_version = OSCheck.get_os_major_version() is_rpm = not OSCheck.is_ubuntu_family() property_prefix = RPM_DEPENDENCIES_PROPERTY if is_rpm else DEB_DEPENDENCIES_PROPERTY cp = ConfigParser.SafeConfigParser() with open(OS_PACKAGE_DEPENDENCIES) as fp: cp.readfp(FakePropertiesHeader(fp)) properties = dict(cp.items(FakePropertiesHeader.FAKE_SECTION_NAME)) packages_string = None postfixes = [os_family+str(ver) for ver in range(int(os_version),0,-1)+['']]+[''] for postfix in postfixes: property_name = property_prefix + postfix if property_name in properties: packages_string = properties[property_name] break if packages_string is None: err_msg = "No os dependencies found. " if self.skip_dependencies: logger.warn(err_msg) else: raise Exception(err_msg) packages_string = re.sub('Requires\s*:','',packages_string) packages_string = re.sub('\\\\n','',packages_string) packages_string = re.sub('\s','',packages_string) packages_string = re.sub('[()]','',packages_string) if self.skip_dependencies: var = raw_input("Please confirm you have the following packages installed {0} (y/n): ".format(packages_string)) if var.lower() != "y" and var.lower() != "yes": raise Exception("User canceled the installation.") return pacakges = packages_string.split(',') for package in pacakges: split_parts = re.split('[><=]', package) package_name = split_parts[0] Utils.install_package(package_name) def run_script(self, script_name, args): bash_args = [] if self.verbose: bash_args.append("-x") Utils.os_call(["bash"] + bash_args + [script_name] + args, env={ROOT_FOLDER_ENV_VARIABLE: self.root_folder}, logoutput=True) class TargzInstaller(Installer): def extract_archive(self): Utils.os_call(['tar','--no-same-owner', '-xvf', self.archive_name, '-C', self.root_folder+os.sep], logoutput=False) class Runner: def parse_opts(self): parser = OptionParser(usage=USAGE, description=DESCRIPTION) parser.add_option("-v", "--verbose", dest="verbose", action="store_true", help="sets output level to more detailed") parser.add_option("-r", "--root-folder", dest="root_folder", default="/", help="root folder to install Ambari to. E.g.: /opt") parser.add_option("-d", "--dependencies-skip", dest="skip_dependencies", action="store_true", help="the script won't install the package dependencies. Please make sure to install them manually.") (self.options, args) = parser.parse_args() if len(args) != 1: help = parser.print_help() sys.exit(1) self.url = args[0] @staticmethod def setup_logger(verbose): logging_level = logging.DEBUG if verbose else logging.INFO logger.setLevel(logging_level) formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s') stdout_handler = logging.StreamHandler(sys.stdout) stdout_handler.setLevel(logging_level) stdout_handler.setFormatter(formatter) logger.addHandler(stdout_handler) def run(self): self.parse_opts() Runner.setup_logger(self.options.verbose) Utils.verbose = self.options.verbose # TODO: check if ends with tar.gz? targz_installer = TargzInstaller(self.url, self.options.root_folder, self.options.verbose, self.options.skip_dependencies) targz_installer.run() if __name__ == '__main__': Runner().run()