PageRenderTime 23ms CodeModel.GetById 14ms RepoModel.GetById 1ms app.codeStats 0ms

/silversupport/shell.py

https://bitbucket.org/ianb/silverlining/
Python | 151 lines | 125 code | 6 blank | 20 comment | 9 complexity | b6eb5f76511e338ae3d9f40b250b20bd MD5 | raw file
Possible License(s): GPL-2.0
  1. """Simple shell routines"""
  2. import os
  3. import re
  4. import subprocess
  5. __all__ = ['ssh', 'run', 'apt_install', 'shell_escape',
  6. 'conditional_shell_escape', 'overwrite_file',
  7. 'add_to_file']
  8. def ssh(user, host, command, **kw):
  9. """Runs ssh to the given user/host, and runs the command.
  10. Any extra keyword arguments are passed to ``run``.
  11. This will use sudo for some users, and ssh in directory in other cases.
  12. """
  13. if user == 'www-data':
  14. # This is a bit tricky:
  15. user = 'www-mgr'
  16. if isinstance(command, (list, tuple)):
  17. command = ' '.join(conditional_shell_escape(i) for i in command)
  18. else:
  19. command = conditional_shell_escape(command)
  20. command = 'sudo -H -u www-data %s' % command
  21. elif isinstance(command, (list, tuple)):
  22. command = ' '.join(conditional_shell_escape(i) for i in command)
  23. ssh_args = list(kw.pop('ssh_args', []))
  24. strict_host_key_checking = kw.pop('strict_host_key_checking', False)
  25. if not strict_host_key_checking:
  26. ssh_args.extend(['-o', 'StrictHostKeyChecking=no'])
  27. return run(['ssh'] + ssh_args + ['-l', user, host, command], **kw)
  28. def run(command, extra_env=None, stdin=None,
  29. capture_stdout=False, capture_stderr=False,
  30. cwd=None, fail_on_error=False):
  31. """Runs a command.
  32. The command may be a string or (preferred) a list of
  33. command+arguments. extra_env is extra environmental keys to set.
  34. stdin is a string to pass in. You can capture stdout and/or
  35. stderr; if you do not they are streamed directly. Similarly if
  36. you do not give stdin, then the command is attached to the regular
  37. stdin.
  38. This always return ``(stdout, stderr, returncode)``. The first
  39. two may be None if you didn't capture them.
  40. """
  41. env = os.environ.copy()
  42. env['LANG'] = 'C'
  43. if extra_env:
  44. env.update(extra_env)
  45. kw = {}
  46. if stdin:
  47. kw['stdin'] = subprocess.PIPE
  48. if capture_stdout:
  49. kw['stdout'] = subprocess.PIPE
  50. if capture_stderr:
  51. kw['stderr'] = subprocess.PIPE
  52. if cwd:
  53. kw['cwd'] = cwd
  54. for key, value in env.items():
  55. assert isinstance(value, str), "bad value %r: %r" % (key, value)
  56. for v in command:
  57. assert isinstance(v, str), "bad command argument %r" % v
  58. try:
  59. proc = subprocess.Popen(
  60. command, env=env, **kw)
  61. except OSError, e:
  62. raise OSError('Error while running command "%s": %s' % (
  63. ' '.join(conditional_shell_escape(i) for i in command), e))
  64. stdout, stderr = proc.communicate(stdin or '')
  65. if fail_on_error and proc.returncode:
  66. if stderr:
  67. msg = '; stderr:\n%s' % stderr
  68. else:
  69. msg = ''
  70. raise OSError(
  71. 'Error while running command "%s": returncode %s%s'
  72. % (' '.join(conditional_shell_escape(i) for i in command),
  73. proc.returncode, msg))
  74. return stdout, stderr, proc.returncode
  75. def apt_install(packages, **kw):
  76. """Install the given list of packages"""
  77. return run(['apt-get', 'install', '-q=2', '-y', '--force-yes',
  78. '--no-install-recommends'] + packages, **kw)
  79. _end_quote_re = re.compile(r"^('*)(.*?)('*)$", re.S)
  80. _quote_re = re.compile("'+")
  81. def shell_escape(arg):
  82. """Escapes an argument for the shell"""
  83. end_quotes_match = _end_quote_re.match(arg)
  84. inner = end_quotes_match.group(2)
  85. inner = _quote_re.sub(lambda m: "'%s'" % m.group(0).replace("'", "\\'"),
  86. inner)
  87. return ("'" + end_quotes_match.group(1).replace("'", "\\'")
  88. + inner + end_quotes_match.group(3).replace("'", "\\'") + "'")
  89. def conditional_shell_escape(arg):
  90. """Escapes an argument, unless it's obvious it doesn't need
  91. escaping"""
  92. if re.match(r'^[a-zA-Z0-9_,./-]+$', arg):
  93. return arg
  94. else:
  95. return shell_escape(arg)
  96. def overwrite_file(dest, source_text=None, source_file=None):
  97. """Overwrite a file on the system"""
  98. if source_file:
  99. assert not source_text
  100. fp = open(source_file, 'rb')
  101. source_text = fp.read()
  102. fp.close()
  103. if os.path.exists(dest):
  104. fp = open(dest, 'rb')
  105. cur_text = fp.read()
  106. fp.close()
  107. if cur_text == source_text:
  108. return
  109. new = open(dest+'.silver-back', 'wb')
  110. new.write(cur_text)
  111. new.close()
  112. ## FIXME: is writing over the file sufficient to keep its permissions?
  113. fp = open(dest, 'wb')
  114. fp.write(source_text)
  115. fp.close()
  116. def add_to_file(dest, source_text, add_newline=True):
  117. """Write the text to the end of the dest, or do nothing if the text
  118. is found in the file already (anywhere in the file)"""
  119. fp = open(dest, 'rb')
  120. content = fp.read()
  121. fp.close()
  122. if source_text in content:
  123. return
  124. if not content.endswith('\n') and add_newline:
  125. content += '\n'
  126. content += source_text
  127. fp = open(dest, 'wb')
  128. fp.write(content)
  129. fp.close()