install_ambari_tarball.py 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250
  1. #!/usr/bin/env python2
  2. '''
  3. Licensed to the Apache Software Foundation (ASF) under one
  4. or more contributor license agreements. See the NOTICE file
  5. distributed with this work for additional information
  6. regarding copyright ownership. The ASF licenses this file
  7. to you under the Apache License, Version 2.0 (the
  8. "License"); you may not use this file except in compliance
  9. with the License. You may obtain a copy of the License at
  10. http://www.apache.org/licenses/LICENSE-2.0
  11. Unless required by applicable law or agreed to in writing, software
  12. distributed under the License is distributed on an "AS IS" BASIS,
  13. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. See the License for the specific language governing permissions and
  15. limitations under the License.
  16. '''
  17. import re
  18. import os
  19. import sys
  20. import logging
  21. import subprocess
  22. from optparse import OptionParser
  23. import ConfigParser
  24. USAGE = "Usage: %prog [OPTION]... URL"
  25. DESCRIPTION = "URL should point to full tar.gz location e.g.: https://public-repo-1.hortonworks.com/something/ambari-server.tar.gz"
  26. logger = logging.getLogger("install_ambari_tarball")
  27. PREINST_SCRIPT = "preinst"
  28. PRERM_SCRIPT = "prerm"
  29. POSTINST_SCRIPT = "postinst"
  30. POSTRM_SCRIPT = "postrm"
  31. OS_CHECK = "os_check.py"
  32. OS_PACKAGE_DEPENDENCIES = "dependencies.properties"
  33. OS_FAMILY_DESCRIPTION = "resources/os_family.json"
  34. RPM_DEPENDENCIES_PROPERTY = "rpm.dependency.list"
  35. DEB_DEPENDENCIES_PROPERTY = "deb.dependency.list"
  36. FILES_TO_DOWNLOAD = [PREINST_SCRIPT, PRERM_SCRIPT, POSTINST_SCRIPT, POSTRM_SCRIPT, OS_CHECK, OS_FAMILY_DESCRIPTION, OS_PACKAGE_DEPENDENCIES]
  37. ROOT_FOLDER_ENV_VARIABLE = "RPM_INSTALL_PREFIX"
  38. class Utils:
  39. verbose = False
  40. @staticmethod
  41. def os_call(command, logoutput=None, env={}):
  42. shell = not isinstance(command, list)
  43. print_output = logoutput==True or (logoutput==None and Utils.verbose)
  44. if not print_output:
  45. stdout = subprocess.PIPE
  46. stderr = subprocess.STDOUT
  47. else:
  48. stdout = stderr = None
  49. logger.info("Running '{0}'".format(command))
  50. proc = subprocess.Popen(command, shell=shell, stdout=stdout, stderr=stderr, env=env)
  51. if not print_output:
  52. out = proc.communicate()[0].strip('\n')
  53. else:
  54. proc.wait()
  55. out = None
  56. code = proc.returncode
  57. if code:
  58. err_msg = ("Execution of '%s'\n returned %d. %s") % (command, code, out)
  59. raise OsCallFailure(err_msg)
  60. return out
  61. @staticmethod
  62. def install_package(name):
  63. from os_check import OSCheck
  64. logger.info("Checking for existance of {0} dependency package".format(name))
  65. is_rpm = not OSCheck.is_ubuntu_family()
  66. if is_rpm:
  67. is_installed_cmd = ['rpm', '-q'] + [name]
  68. install_cmd = ['sudo', 'yum', '-y', 'install'] + [name]
  69. else:
  70. is_installed_cmd = ['dpkg', '-s'] + [name]
  71. install_cmd = ['sudo', 'apt-get', '-y', 'install'] + [name]
  72. try:
  73. Utils.os_call(is_installed_cmd, logoutput=False)
  74. logger.info("Package {0} is already installed. Skipping installation.".format(name))
  75. except OsCallFailure:
  76. logger.info("Package {0} is not installed. Installing it...".format(name))
  77. Utils.os_call(install_cmd)
  78. class FakePropertiesHeader(object):
  79. """
  80. Hacky class to parse properties file without sections.
  81. see http://stackoverflow.com/questions/2819696/module-to-use-when-parsing-properties-file-in-python/2819788#2819788
  82. """
  83. FAKE_SECTION_NAME = 'section'
  84. def __init__(self, fp):
  85. self.fp = fp
  86. self.sechead = '[{0}]\n'.format(FakePropertiesHeader.FAKE_SECTION_NAME)
  87. def readline(self):
  88. if self.sechead:
  89. try:
  90. return self.sechead
  91. finally:
  92. self.sechead = None
  93. else:
  94. return self.fp.readline()
  95. class OsCallFailure(RuntimeError):
  96. pass
  97. class Installer:
  98. def __init__(self, archive_url, root_folder, verbose, skip_dependencies):
  99. splited_url = archive_url.split('/')
  100. self.archive_name = splited_url[-1]
  101. self.base_url = '/'.join(splited_url[0:-1])
  102. self.root_folder = root_folder
  103. self.verbose = verbose
  104. self.skip_dependencies = skip_dependencies
  105. def download_files(self, files_list):
  106. for name in files_list:
  107. dirname = os.path.dirname(name)
  108. if dirname:
  109. Utils.os_call(["mkdir", "-p", dirname])
  110. url = "{0}/{1}".format(self.base_url, name)
  111. logger.info("Downloading {0}".format(url))
  112. Utils.os_call(["wget", "-O", name, url])
  113. def run(self):
  114. self.download_files([self.archive_name] +FILES_TO_DOWNLOAD) # [self.archive_name] +
  115. self.check_dependencies()
  116. self.run_script(PRERM_SCRIPT, ["remove"]) # in case we are upgrading
  117. self.run_script(POSTRM_SCRIPT, ["remove"]) # in case we are upgrading
  118. self.run_script(PREINST_SCRIPT, ["install"])
  119. self.extract_archive()
  120. self.run_script(POSTINST_SCRIPT, ["configure"])
  121. def check_dependencies(self):
  122. from os_check import OSCheck
  123. os_family = OSCheck.get_os_family()
  124. os_version = OSCheck.get_os_major_version()
  125. is_rpm = not OSCheck.is_ubuntu_family()
  126. property_prefix = RPM_DEPENDENCIES_PROPERTY if is_rpm else DEB_DEPENDENCIES_PROPERTY
  127. cp = ConfigParser.SafeConfigParser()
  128. with open(OS_PACKAGE_DEPENDENCIES) as fp:
  129. cp.readfp(FakePropertiesHeader(fp))
  130. properties = dict(cp.items(FakePropertiesHeader.FAKE_SECTION_NAME))
  131. packages_string = None
  132. postfixes = [os_family+str(ver) for ver in range(int(os_version),0,-1)+['']]+['']
  133. for postfix in postfixes:
  134. property_name = property_prefix + postfix
  135. if property_name in properties:
  136. packages_string = properties[property_name]
  137. break
  138. if packages_string is None:
  139. err_msg = "No os dependencies found. "
  140. if self.skip_dependencies:
  141. logger.warn(err_msg)
  142. else:
  143. raise Exception(err_msg)
  144. packages_string = re.sub('Requires\s*:','',packages_string)
  145. packages_string = re.sub('\\\\n','',packages_string)
  146. packages_string = re.sub('\s','',packages_string)
  147. packages_string = re.sub('[()]','',packages_string)
  148. if self.skip_dependencies:
  149. var = raw_input("Please confirm you have the following packages installed {0} (y/n): ".format(packages_string))
  150. if var.lower() != "y" and var.lower() != "yes":
  151. raise Exception("User canceled the installation.")
  152. return
  153. pacakges = packages_string.split(',')
  154. for package in pacakges:
  155. split_parts = re.split('[><=]', package)
  156. package_name = split_parts[0]
  157. Utils.install_package(package_name)
  158. def run_script(self, script_name, args):
  159. bash_args = []
  160. if self.verbose:
  161. bash_args.append("-x")
  162. Utils.os_call(["bash"] + bash_args + [script_name] + args, env={ROOT_FOLDER_ENV_VARIABLE: self.root_folder}, logoutput=True)
  163. class TargzInstaller(Installer):
  164. def extract_archive(self):
  165. Utils.os_call(['tar','--no-same-owner', '-xvf', self.archive_name, '-C', self.root_folder+os.sep], logoutput=False)
  166. class Runner:
  167. def parse_opts(self):
  168. parser = OptionParser(usage=USAGE, description=DESCRIPTION)
  169. parser.add_option("-v", "--verbose", dest="verbose", action="store_true",
  170. help="sets output level to more detailed")
  171. parser.add_option("-r", "--root-folder", dest="root_folder", default="/",
  172. help="root folder to install Ambari to. E.g.: /opt")
  173. parser.add_option("-d", "--dependencies-skip", dest="skip_dependencies", action="store_true",
  174. help="the script won't install the package dependencies. Please make sure to install them manually.")
  175. (self.options, args) = parser.parse_args()
  176. if len(args) != 1:
  177. help = parser.print_help()
  178. sys.exit(1)
  179. self.url = args[0]
  180. @staticmethod
  181. def setup_logger(verbose):
  182. logging_level = logging.DEBUG if verbose else logging.INFO
  183. logger.setLevel(logging_level)
  184. formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s')
  185. stdout_handler = logging.StreamHandler(sys.stdout)
  186. stdout_handler.setLevel(logging_level)
  187. stdout_handler.setFormatter(formatter)
  188. logger.addHandler(stdout_handler)
  189. def run(self):
  190. self.parse_opts()
  191. Runner.setup_logger(self.options.verbose)
  192. Utils.verbose = self.options.verbose
  193. # TODO: check if ends with tar.gz?
  194. targz_installer = TargzInstaller(self.url, self.options.root_folder, self.options.verbose, self.options.skip_dependencies)
  195. targz_installer.run()
  196. if __name__ == '__main__':
  197. Runner().run()