app_reg.py 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279
  1. #!/usr/bin/env python
  2. # Licensed to Cloudera, Inc. under one
  3. # or more contributor license agreements. See the NOTICE file
  4. # distributed with this work for additional information
  5. # regarding copyright ownership. Cloudera, Inc. licenses this file
  6. # to you under the Apache License, Version 2.0 (the
  7. # "License"); you may not use this file except in compliance
  8. # with the License. You may obtain a copy of the License at
  9. #
  10. # http://www.apache.org/licenses/LICENSE-2.0
  11. #
  12. # Unless required by applicable law or agreed to in writing, software
  13. # distributed under the License is distributed on an "AS IS" BASIS,
  14. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  15. # See the License for the specific language governing permissions and
  16. # limitations under the License.
  17. """
  18. A tool to manage Hue applications. This does not stop/restart a
  19. running Hue instance.
  20. Usage:
  21. %(PROG_NAME)s [flags] --install <path_to_app> [<path_to_app> ...] [--relative-paths]
  22. To register and install new application(s).
  23. Add '--relative-paths' to the end of the args list to force the app manager to register the new application using its path relative to the hue root.
  24. %(PROG_NAME)s [flags] --remove <application_name>
  25. To unregister and remove an installed application.
  26. %(PROG_NAME)s [flags] --list
  27. To list all registered applications.
  28. %(PROG_NAME)s [flags] --sync
  29. Synchronize all registered applications with the Hue environment.
  30. Useful after a `make clean'.
  31. Optional flags:
  32. --debug Turns on debugging output
  33. """
  34. from __future__ import print_function
  35. import getopt
  36. import logging
  37. import os
  38. import sys
  39. import subprocess
  40. import build
  41. import common
  42. import pth
  43. import registry
  44. from functools import reduce
  45. PROG_NAME = sys.argv[0]
  46. LOG = logging.getLogger()
  47. LOG_LEVEL = logging.INFO
  48. LOG_FORMAT = "%(message)s"
  49. DO_INSTALL = 'do_install'
  50. DO_REMOVE = 'do_remove'
  51. DO_LIST = 'do_list'
  52. DO_SYNC = 'do_sync'
  53. DO_COLLECTSTATIC = 'do_collectstatic'
  54. def usage(msg=None):
  55. """Print the usage with an optional message. And exit."""
  56. global __doc__
  57. if msg is not None:
  58. print(msg, file=sys.stderr)
  59. print(__doc__ % dict(PROG_NAME=PROG_NAME), file=sys.stderr)
  60. sys.exit(1)
  61. def get_app_info(app_loc):
  62. """
  63. get_app_info(app_loc) -> (app_name, version, description)
  64. Runs the app's setup.py to get the info. May raise ValueError and OSError.
  65. """
  66. if not os.path.isdir(app_loc):
  67. msg = "Not a directory: %s" % (app_loc,)
  68. LOG.error(msg)
  69. raise ValueError(msg)
  70. save_cwd = os.getcwd()
  71. os.chdir(app_loc)
  72. try:
  73. cmdv = [ common.ENV_PYTHON, 'setup.py',
  74. '--name', '--version', '--description',
  75. '--author' ]
  76. LOG.debug("Running '%s'" % (' '.join(cmdv),))
  77. popen = subprocess.Popen(cmdv, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
  78. res = popen.wait()
  79. stdout, stderr = popen.communicate()
  80. # Cmd failure?
  81. if isinstance(stdout, bytes):
  82. stdout = stdout.decode('utf-8')
  83. if isinstance(stderr, bytes):
  84. stderr = stderr.decode('utf-8')
  85. if res != 0:
  86. LOG.error("Error getting application info from %s:\n%s" % (app_loc, stderr))
  87. raise OSError(stderr)
  88. LOG.debug("Command output:\n<<<\n%s\n>>>" % (stdout,))
  89. return stdout.split('\n')[:4]
  90. finally:
  91. os.chdir(save_cwd)
  92. def _do_install_one(reg, app_loc, relative_path):
  93. """Install one app, without saving. Returns True/False."""
  94. LOG.info("=== Installing app at %s" % (app_loc,))
  95. try:
  96. # Relative to cwd.
  97. app_loc = os.path.realpath(app_loc)
  98. app_name, version, desc, author = get_app_info(app_loc)
  99. except (ValueError, OSError) as ex:
  100. LOG.error(ex)
  101. return False
  102. app = registry.HueApp(app_name, version, app_loc, desc, author)
  103. if relative_path:
  104. app.use_rel_path()
  105. else:
  106. app.use_abs_path()
  107. if reg.contains(app):
  108. LOG.warn("=== %s is already installed" % (app,))
  109. return True
  110. return reg.register(app) and build.make_app(app) and app.install_conf()
  111. def do_install(app_loc_list, relative_paths=False):
  112. """Install the apps. Returns True/False."""
  113. reg = registry.AppRegistry()
  114. for app_loc in app_loc_list:
  115. if not _do_install_one(reg, app_loc, relative_paths):
  116. return False
  117. reg.save()
  118. return do_sync(reg) and do_collectstatic()
  119. def do_list():
  120. """List all apps. Returns True/False."""
  121. reg = registry.AppRegistry()
  122. apps = reg.get_all_apps()
  123. LOG.info("%-18s %-7s %-15s %s" % ('Name', 'Version', 'Author', 'Path'))
  124. LOG.info("%s %s %s %s" % ('-' * 18, '-' * 7, '-' * 15, '-' * 35))
  125. for app in sorted(apps):
  126. LOG.info("%-18s %-7s %-15s %s" % (app.name, app.version, app.author, app.path))
  127. return True
  128. def do_remove(app_name):
  129. """Uninstall the given app. Returns True/False."""
  130. # TODO(bc) Does not detect dependency. The app to be uninstalled could be a
  131. # pre-req for other apps, as defined in various setup.py files.
  132. LOG.info("=== Uninstalling %s" % (app_name,))
  133. reg = registry.AppRegistry()
  134. try:
  135. app = reg.unregister(app_name)
  136. except KeyError:
  137. LOG.error("%s is not installed" % (app_name,))
  138. return False
  139. app.uninstall_conf()
  140. reg.save()
  141. # Update the pth file
  142. try:
  143. pthfile = pth.PthFile()
  144. pthfile.remove(app)
  145. pthfile.save()
  146. return True
  147. except (OSError, SystemError) as ex:
  148. LOG.error("Failed to update the .pth file. Please fix any problem and run "
  149. "`%s --sync'\n%s" % (PROG_NAME, ex))
  150. return False
  151. def do_sync(reg=None):
  152. """Sync apps with virtualenv. Returns True/False."""
  153. if not reg:
  154. reg = registry.AppRegistry()
  155. apps = reg.get_all_apps()
  156. try:
  157. pthfile = pth.PthFile()
  158. pthfile.sync(apps)
  159. pthfile.save()
  160. build.make_syncdb()
  161. return True
  162. except (OSError, SystemError) as ex:
  163. LOG.error("Failed to update the .pth file. Please fix any problem and run "
  164. "`%s --sync'\n%s" % (PROG_NAME, ex))
  165. return False
  166. def do_collectstatic():
  167. """Collects the static files. Returns True/False."""
  168. try:
  169. build.make_collectstatic()
  170. return True
  171. except (OSError, SystemError) as ex:
  172. LOG.error("Failed to collect the static files. Please fix any problem and run "
  173. "`%s --collectstatic'\n%s" % (PROG_NAME, ex))
  174. return False
  175. def main():
  176. action = None
  177. app = None
  178. # Option parsing
  179. try:
  180. opts, tail = getopt.getopt(sys.argv[1:],
  181. 'ir:lds',
  182. ('install', 'remove=', 'list', 'debug', 'sync'))
  183. except getopt.GetoptError as ex:
  184. usage(str(ex))
  185. def verify_action(current, new_val):
  186. if current is not None:
  187. usage()
  188. return new_val
  189. for opt, arg in opts:
  190. if opt in ('-i', '--install'):
  191. action = verify_action(action, DO_INSTALL)
  192. elif opt in ('-r', '--remove'):
  193. action = verify_action(action, DO_REMOVE)
  194. app = arg
  195. elif opt in ('-l', '--list'):
  196. action = verify_action(action, DO_LIST)
  197. elif opt in ('-s', '--sync'):
  198. action = verify_action(action, DO_SYNC)
  199. elif opt in ('-c', '--collectstatic'):
  200. action = verify_action(action, DO_COLLECTSTATIC)
  201. elif opt in ('-d', '--debug'):
  202. global LOG_LEVEL
  203. LOG_LEVEL = logging.DEBUG
  204. if action == DO_INSTALL:
  205. # ['..', '--relative-paths', 'a', 'b'] => True
  206. # ['..', 'a', 'b'] -> False
  207. relative_paths = reduce(lambda accum, x: accum or x, [x in ['--relative-paths'] for x in tail])
  208. app_loc_list = [x for x in tail if x not in ['--relative-paths']]
  209. elif len(tail) != 0:
  210. usage("Unknown trailing arguments: %s" % ' '.join(tail))
  211. if action is None:
  212. usage()
  213. # Setup logging
  214. logging.basicConfig(level=LOG_LEVEL, format=LOG_FORMAT)
  215. # Dispatch
  216. if action == DO_INSTALL:
  217. ok = do_install(app_loc_list, relative_paths)
  218. elif action == DO_REMOVE:
  219. ok = do_remove(app)
  220. elif action == DO_LIST:
  221. ok = do_list()
  222. elif action == DO_SYNC:
  223. ok = do_sync()
  224. elif action == DO_COLLECTSTATIC:
  225. ok = do_collectstatic()
  226. if ok:
  227. return 0
  228. return 2
  229. if __name__ == '__main__':
  230. sys.exit(main())