Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

192

193

194

195

196

197

198

199

200

201

202

203

204

205

206

207

208

209

210

211

212

213

214

215

216

217

# (c) 2016 RedHat 

# 

# This file is part of Ansible. 

# 

# Ansible is free software: you can redistribute it and/or modify 

# it under the terms of the GNU General Public License as published by 

# the Free Software Foundation, either version 3 of the License, or 

# (at your option) any later version. 

# 

# Ansible is distributed in the hope that it will be useful, 

# but WITHOUT ANY WARRANTY; without even the implied warranty of 

# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

# GNU General Public License for more details. 

# 

# You should have received a copy of the GNU General Public License 

# along with Ansible. If not, see <http://www.gnu.org/licenses/>. 

from __future__ import (absolute_import, division, print_function) 

__metaclass__ = type 

 

import os 

import os.path 

import random 

import re 

import time 

 

import ansible.constants as C 

from ansible.errors import AnsibleError 

from ansible.module_utils.six import text_type 

from ansible.module_utils.six.moves import shlex_quote 

from ansible.module_utils._text import to_native 

from ansible.plugins import AnsiblePlugin 

 

_USER_HOME_PATH_RE = re.compile(r'^~[_.A-Za-z0-9][-_.A-Za-z0-9]*$') 

 

 

class ShellBase(AnsiblePlugin): 

def __init__(self): 

 

super(ShellBase, self).__init__() 

 

self.env = {} 

42 ↛ 43line 42 didn't jump to line 43, because the condition on line 42 was never true if C.DEFAULT_MODULE_SET_LOCALE: 

module_locale = C.DEFAULT_MODULE_LANG 

self.env = {'LANG': module_locale, 

'LC_ALL': module_locale, 

'LC_MESSAGES': module_locale} 

 

self.tmpdir = None 

 

def _normalize_system_tmpdirs(self): 

# Normalize the tmp directory strings. We don't use expanduser/expandvars because those 

# can vary between remote user and become user. Therefore the safest practice will be for 

# this to always be specified as full paths) 

normalized_paths = [d.rstrip('/') for d in self.get_option('system_tmpdirs')] 

 

# Make sure all system_tmpdirs are absolute otherwise they'd be relative to the login dir 

# which is almost certainly going to fail in a cornercase. 

58 ↛ 59line 58 didn't jump to line 59, because the condition on line 58 was never true if not all(os.path.isabs(d) for d in normalized_paths): 

raise AnsibleError('The configured system_tmpdirs contains a relative path: {0}. All' 

' system_tmpdirs must be absolute'.format(to_native(normalized_paths))) 

 

self.set_option('system_tmpdirs', normalized_paths) 

 

def set_options(self, task_keys=None, var_options=None, direct=None): 

 

super(ShellBase, self).set_options(task_keys=task_keys, var_options=var_options, direct=direct) 

 

# set env 

self.env.update(self.get_option('environment')) 

 

# We can remove the try: except in the future when we make ShellBase a proper subset of 

# *all* shells. Right now powershell and third party shells which do not use the 

# shell_common documentation fragment (and so do not have system_tmpdirs) will fail 

try: 

self._normalize_system_tmpdirs() 

except AnsibleError: 

pass 

 

def env_prefix(self, **kwargs): 

return ' '.join(['%s=%s' % (k, shlex_quote(text_type(v))) for k, v in kwargs.items()]) 

 

def join_path(self, *args): 

return os.path.join(*args) 

 

# some shells (eg, powershell) are snooty about filenames/extensions, this lets the shell plugin have a say 

def get_remote_filename(self, pathname): 

base_name = os.path.basename(pathname.strip()) 

return base_name.strip() 

 

def path_has_trailing_slash(self, path): 

return path.endswith('/') 

 

def chmod(self, paths, mode): 

cmd = ['chmod', mode] 

cmd.extend(paths) 

cmd = [shlex_quote(c) for c in cmd] 

 

return ' '.join(cmd) 

 

def chown(self, paths, user): 

cmd = ['chown', user] 

cmd.extend(paths) 

cmd = [shlex_quote(c) for c in cmd] 

 

return ' '.join(cmd) 

 

def set_user_facl(self, paths, user, mode): 

"""Only sets acls for users as that's really all we need""" 

cmd = ['setfacl', '-m', 'u:%s:%s' % (user, mode)] 

cmd.extend(paths) 

cmd = [shlex_quote(c) for c in cmd] 

 

return ' '.join(cmd) 

 

def remove(self, path, recurse=False): 

path = shlex_quote(path) 

cmd = 'rm -f ' 

118 ↛ 120line 118 didn't jump to line 120, because the condition on line 118 was never false if recurse: 

cmd += '-r ' 

return cmd + "%s %s" % (path, self._SHELL_REDIRECT_ALLNULL) 

 

def exists(self, path): 

cmd = ['test', '-e', shlex_quote(path)] 

return ' '.join(cmd) 

 

def mkdtemp(self, basefile=None, system=False, mode=0o700, tmpdir=None): 

127 ↛ 128line 127 didn't jump to line 128, because the condition on line 127 was never true if not basefile: 

basefile = 'ansible-tmp-%s-%s' % (time.time(), random.randint(0, 2**48)) 

 

# When system is specified we have to create this in a directory where 

# other users can read and access the tmp directory. 

# This is because we use system to create tmp dirs for unprivileged users who are 

# sudo'ing to a second unprivileged user. 

# The 'system_tmpdirs' setting defines dirctories we can use for this purpose 

# the default are, /tmp and /var/tmp. 

# So we only allow one of those locations if system=True, using the 

# passed in tmpdir if it is valid or the first one from the setting if not. 

 

139 ↛ 140line 139 didn't jump to line 140, because the condition on line 139 was never true if system: 

tmpdir = tmpdir.rstrip('/') 

 

if tmpdir in self.get_option('system_tmpdirs'): 

basetmpdir = tmpdir 

else: 

basetmpdir = self.get_option('system_tmpdirs')[0] 

else: 

147 ↛ 148line 147 didn't jump to line 148, because the condition on line 147 was never true if tmpdir is None: 

basetmpdir = self.get_option('remote_tmp') 

else: 

basetmpdir = tmpdir 

 

basetmp = self.join_path(basetmpdir, basefile) 

 

cmd = 'mkdir -p %s echo %s %s' % (self._SHELL_SUB_LEFT, basetmp, self._SHELL_SUB_RIGHT) 

cmd += ' %s echo %s=%s echo %s %s' % (self._SHELL_AND, basefile, self._SHELL_SUB_LEFT, basetmp, self._SHELL_SUB_RIGHT) 

 

# change the umask in a subshell to achieve the desired mode 

# also for directories created with `mkdir -p` 

159 ↛ 163line 159 didn't jump to line 163, because the condition on line 159 was never false if mode: 

tmp_umask = 0o777 & ~mode 

cmd = '%s umask %o %s %s %s' % (self._SHELL_GROUP_LEFT, tmp_umask, self._SHELL_AND, cmd, self._SHELL_GROUP_RIGHT) 

 

return cmd 

 

def expand_user(self, user_home_path, username=''): 

''' Return a command to expand tildes in a path 

 

It can be either "~" or "~username". We just ignore $HOME 

We use the POSIX definition of a username: 

http://pubs.opengroup.org/onlinepubs/000095399/basedefs/xbd_chap03.html#tag_03_426 

http://pubs.opengroup.org/onlinepubs/000095399/basedefs/xbd_chap03.html#tag_03_276 

 

Falls back to 'current workind directory' as we assume 'home is where the remote user ends up' 

''' 

 

# Check that the user_path to expand is safe 

177 ↛ 178line 177 didn't jump to line 178, because the condition on line 177 was never true if user_home_path != '~': 

if not _USER_HOME_PATH_RE.match(user_home_path): 

# shlex_quote will make the shell return the string verbatim 

user_home_path = shlex_quote(user_home_path) 

181 ↛ 183line 181 didn't jump to line 183, because the condition on line 181 was never true elif username: 

# if present the user name is appended to resolve "that user's home" 

user_home_path += username 

 

return 'echo %s' % user_home_path 

 

def pwd(self): 

"""Return the working directory after connecting""" 

return 'echo %spwd%s' % (self._SHELL_SUB_LEFT, self._SHELL_SUB_RIGHT) 

 

def build_module_command(self, env_string, shebang, cmd, arg_path=None): 

# don't quote the cmd if it's an empty string, because this will break pipelining mode 

193 ↛ 196line 193 didn't jump to line 196, because the condition on line 193 was never false if cmd.strip() != '': 

cmd = shlex_quote(cmd) 

 

cmd_parts = [] 

197 ↛ 200line 197 didn't jump to line 200, because the condition on line 197 was never false if shebang: 

shebang = shebang.replace("#!", "").strip() 

else: 

shebang = "" 

cmd_parts.extend([env_string.strip(), shebang, cmd]) 

202 ↛ 203line 202 didn't jump to line 203, because the condition on line 202 was never true if arg_path is not None: 

cmd_parts.append(arg_path) 

new_cmd = " ".join(cmd_parts) 

return new_cmd 

 

def append_command(self, cmd, cmd_to_append): 

"""Append an additional command if supported by the shell""" 

 

210 ↛ 213line 210 didn't jump to line 213, because the condition on line 210 was never false if self._SHELL_AND: 

cmd += ' %s %s' % (self._SHELL_AND, cmd_to_append) 

 

return cmd 

 

def wrap_for_exec(self, cmd): 

"""wrap script execution with any necessary decoration (eg '&' for quoted powershell script paths)""" 

return cmd