packstack/packstack/installer/utils/shell.py

165 lines
5.7 KiB
Python

# -*- coding: utf-8 -*-
# Licensed 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 six
import logging
import subprocess
from ..exceptions import ExecuteRuntimeError
from ..exceptions import NetworkError
from ..exceptions import ScriptRuntimeError
from .strings import mask_string
block_fmt = ("\n============= %(title)s ==========\n%(content)s\n"
"======== END OF %(title)s ========")
def execute(cmd, workdir=None, can_fail=True, mask_list=None,
use_shell=False, log=True):
"""
Runs shell command cmd. If can_fail is set to False
ExecuteRuntimeError is raised if command returned non-zero return
code. Otherwise
"""
mask_list = mask_list or []
repl_list = [("'", "'\\''")]
if not isinstance(cmd, six.string_types):
import pipes
masked = ' '.join((pipes.quote(i) for i in cmd))
else:
masked = cmd
masked = mask_string(masked, mask_list, repl_list)
if log:
logging.info("Executing command:\n%s" % masked)
environ = os.environ
environ['LANG'] = 'en_US.UTF8'
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE,
stderr=subprocess.PIPE, cwd=workdir,
shell=use_shell, close_fds=True,
env=environ)
out, err = proc.communicate()
if not isinstance(out, six.text_type):
out = out.decode('utf-8')
if not isinstance(err, six.text_type):
err = err.decode('utf-8')
masked_out = mask_string(out, mask_list, repl_list)
masked_err = mask_string(err, mask_list, repl_list)
if log:
logging.debug(block_fmt % {'title': 'STDOUT', 'content': masked_out})
if proc.returncode:
if log:
logging.debug(block_fmt % {'title': 'STDERR',
'content': masked_err})
if can_fail:
msg = ('Failed to execute command, '
'stdout: %s\nstderr: %s' %
(masked_out, masked_err))
raise ExecuteRuntimeError(msg, stdout=out, stderr=err)
return proc.returncode, out
class ScriptRunner(object):
_pkg_search = 'rpm -q --whatprovides'
def __init__(self, ip=None):
self.script = []
self.ip = ip
def append(self, s):
self.script.append(s)
def clear(self):
self.script = []
def execute(self, can_fail=True, mask_list=None, log=True):
mask_list = mask_list or []
repl_list = [("'", "'\\''")]
script = "\n".join(self.script)
masked = mask_string(script, mask_list, repl_list)
if log:
logging.info("[%s] Executing script:\n%s" %
(self.ip or 'localhost', masked))
_PIPE = subprocess.PIPE # pylint: disable=E1101
if self.ip:
cmd = ["ssh", "-o", "StrictHostKeyChecking=no",
"-o", "UserKnownHostsFile=/dev/null",
"root@%s" % self.ip, "bash -x"]
else:
cmd = ["bash", "-x"]
environ = os.environ
environ['LANG'] = 'en_US.UTF8'
obj = subprocess.Popen(cmd, stdin=_PIPE, stdout=_PIPE, stderr=_PIPE,
close_fds=True, shell=False, env=environ)
script = "function t(){ exit $? ; } \n trap t ERR \n %s" % script
out, err = obj.communicate(script.encode('utf-8'))
if not isinstance(out, six.text_type):
out = out.decode('utf-8')
if not isinstance(err, six.text_type):
err = err.decode('utf-8')
masked_out = mask_string(out, mask_list, repl_list)
masked_err = mask_string(err, mask_list, repl_list)
if log:
logging.debug(block_fmt % {'title': 'STDOUT',
'content': masked_out})
if obj.returncode:
if log:
logging.debug(block_fmt % {'title': 'STDERR',
'content': masked_err})
if can_fail:
pattern = (r'^ssh\:')
if re.search(pattern, err):
raise NetworkError(masked_err, stdout=out, stderr=err)
else:
msg = ('Failed to run remote script, '
'stdout: %s\nstderr: %s' %
(masked_out, masked_err))
raise ScriptRuntimeError(msg, stdout=out, stderr=err)
return obj.returncode, out
def template(self, src, dst, varsdict):
with open(src) as fp:
content = fp.read() % varsdict
self.append("cat > %s <<- EOF\n%s\nEOF\n" % (dst, content))
def if_not_exists(self, path, command):
self.append("[ -e %s ] || %s" % (path, command))
def if_exists(self, path, command):
self.append("[ -e %s ] && %s" % (path, command))
def if_installed(self, pkg, command):
self.append("%s %s && %s" % (self._pkg_search, pkg, command))
def if_not_installed(self, pkg, command):
self.append("%s %s || %s" % (self._pkg_search, pkg, command))
def chown(self, target, uid, gid):
self.append("chown %s:%s %s" % (uid, gid, target))
def chmod(self, target, mode):
self.append("chmod %s %s" % (mode, target))