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

218

219

220

221

222

223

224

225

226

227

228

229

230

231

232

233

234

235

236

237

238

239

240

241

242

243

244

245

246

247

248

249

250

251

252

253

254

255

256

257

258

259

260

261

262

263

264

265

266

267

268

269

270

271

272

273

274

275

276

277

278

279

280

281

282

283

284

285

286

#!/usr/bin/env python 

# Copyright: (c) 2017, Ansible Project 

# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) 

from __future__ import (absolute_import, division, print_function) 

 

__metaclass__ = type 

__requires__ = ['ansible'] 

 

try: 

import pkg_resources 

except Exception: 

pass 

 

import fcntl 

import os 

import signal 

import socket 

import sys 

import traceback 

import errno 

import json 

 

from ansible import constants as C 

from ansible.module_utils._text import to_bytes, to_native, to_text 

from ansible.module_utils.six import PY3 

from ansible.module_utils.six.moves import cPickle, StringIO 

from ansible.module_utils.connection import Connection, ConnectionError, send_data, recv_data 

from ansible.module_utils.service import fork_process 

from ansible.playbook.play_context import PlayContext 

from ansible.plugins.loader import connection_loader 

from ansible.utils.path import unfrackpath, makedirs_safe 

from ansible.utils.display import Display 

from ansible.utils.jsonrpc import JsonRpcServer 

 

 

class ConnectionProcess(object): 

''' 

The connection process wraps around a Connection object that manages 

the connection to a remote device that persists over the playbook 

''' 

def __init__(self, fd, play_context, socket_path, original_path, ansible_playbook_pid=None): 

self.play_context = play_context 

self.socket_path = socket_path 

self.original_path = original_path 

 

self.fd = fd 

self.exception = None 

 

self.srv = JsonRpcServer() 

self.sock = None 

 

self.connection = None 

self._ansible_playbook_pid = ansible_playbook_pid 

 

def start(self): 

try: 

messages = list() 

result = {} 

 

messages.append('control socket path is %s' % self.socket_path) 

 

# If this is a relative path (~ gets expanded later) then plug the 

# key's path on to the directory we originally came from, so we can 

# find it now that our cwd is / 

65 ↛ 66line 65 didn't jump to line 66, because the condition on line 65 was never true if self.play_context.private_key_file and self.play_context.private_key_file[0] not in '~/': 

self.play_context.private_key_file = os.path.join(self.original_path, self.play_context.private_key_file) 

self.connection = connection_loader.get(self.play_context.connection, self.play_context, '/dev/null', 

ansible_playbook_pid=self._ansible_playbook_pid) 

self.connection.set_options() 

self.connection._connect() 

self.connection._socket_path = self.socket_path 

self.srv.register(self.connection) 

messages.extend(sys.stdout.getvalue().splitlines()) 

messages.append('connection to remote device started successfully') 

 

self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) 

self.sock.bind(self.socket_path) 

self.sock.listen(1) 

messages.append('local domain socket listeners started successfully') 

except Exception as exc: 

result['error'] = to_text(exc) 

result['exception'] = traceback.format_exc() 

finally: 

result['messages'] = messages 

self.fd.write(json.dumps(result)) 

self.fd.close() 

 

def run(self): 

try: 

90 ↛ 124line 90 didn't jump to line 124, because the condition on line 90 was never false while self.connection.connected: 

signal.signal(signal.SIGALRM, self.connect_timeout) 

signal.signal(signal.SIGTERM, self.handler) 

signal.alarm(C.PERSISTENT_CONNECT_TIMEOUT) 

 

self.exception = None 

(s, addr) = self.sock.accept() 

signal.alarm(0) 

 

signal.signal(signal.SIGALRM, self.command_timeout) 

while True: 

data = recv_data(s) 

if not data: 

break 

 

signal.alarm(self.connection._play_context.timeout) 

resp = self.srv.handle_request(data) 

signal.alarm(0) 

 

send_data(s, to_bytes(resp)) 

 

s.close() 

 

except Exception as e: 

# socket.accept() will raise EINTR if the socket.close() is called 

115 ↛ 119line 115 didn't jump to line 119, because the condition on line 115 was never false if hasattr(e, 'errno'): 

116 ↛ 117line 116 didn't jump to line 117, because the condition on line 116 was never true if e.errno != errno.EINTR: 

self.exception = traceback.format_exc() 

else: 

self.exception = traceback.format_exc() 

 

finally: 

# when done, close the connection properly and cleanup 

# the socket file so it can be recreated 

self.shutdown() 

 

def connect_timeout(self, signum, frame): 

display.display('persistent connection idle timeout triggered, timeout value is %s secs' % C.PERSISTENT_CONNECT_TIMEOUT, log_only=True) 

self.shutdown() 

 

def command_timeout(self, signum, frame): 

display.display('command timeout triggered, timeout value is %s secs' % self.play_context.timeout, log_only=True) 

self.shutdown() 

 

def handler(self, signum, frame): 

display.display('signal handler called with signal %s' % signum, log_only=True) 

self.shutdown() 

 

def shutdown(self): 

""" Shuts down the local domain socket 

""" 

if os.path.exists(self.socket_path): 

try: 

143 ↛ 145line 143 didn't jump to line 145, because the condition on line 143 was never false if self.sock: 

self.sock.close() 

145 ↛ 150line 145 didn't jump to line 150, because the condition on line 145 was never false if self.connection: 

self.connection.close() 

except: 

pass 

finally: 

150 ↛ 154line 150 didn't jump to line 154, because the condition on line 150 was never false if os.path.exists(self.socket_path): 

os.remove(self.socket_path) 

setattr(self.connection, '_socket_path', None) 

setattr(self.connection, '_connected', False) 

display.display('shutdown complete', log_only=True) 

 

 

def main(): 

""" Called to initiate the connect to the remote device 

""" 

rc = 0 

result = {} 

messages = list() 

socket_path = None 

 

# Need stdin as a byte stream 

166 ↛ 167line 166 didn't jump to line 167, because the condition on line 166 was never true if PY3: 

stdin = sys.stdin.buffer 

else: 

stdin = sys.stdin 

 

# Note: update the below log capture code after Display.display() is refactored. 

saved_stdout = sys.stdout 

sys.stdout = StringIO() 

 

try: 

# read the play context data via stdin, which means depickling it 

cur_line = stdin.readline() 

init_data = b'' 

 

while cur_line.strip() != b'#END_INIT#': 

181 ↛ 182line 181 didn't jump to line 182, because the condition on line 181 was never true if cur_line == b'': 

raise Exception("EOF found before init data was complete") 

init_data += cur_line 

cur_line = stdin.readline() 

 

186 ↛ 187line 186 didn't jump to line 187, because the condition on line 186 was never true if PY3: 

pc_data = cPickle.loads(init_data, encoding='bytes') 

else: 

pc_data = cPickle.loads(init_data) 

 

play_context = PlayContext() 

play_context.deserialize(pc_data) 

display.verbosity = play_context.verbosity 

 

except Exception as e: 

rc = 1 

result.update({ 

'error': to_text(e), 

'exception': traceback.format_exc() 

}) 

 

202 ↛ 267line 202 didn't jump to line 267, because the condition on line 202 was never false if rc == 0: 

ssh = connection_loader.get('ssh', class_only=True) 

ansible_playbook_pid = sys.argv[1] 

cp = ssh._create_control_path(play_context.remote_addr, play_context.port, play_context.remote_user, play_context.connection, ansible_playbook_pid) 

 

# create the persistent connection dir if need be and create the paths 

# which we will be using later 

tmp_path = unfrackpath(C.PERSISTENT_CONTROL_PATH_DIR) 

makedirs_safe(tmp_path) 

 

lock_path = unfrackpath("%s/.ansible_pc_lock" % tmp_path) 

socket_path = unfrackpath(cp % dict(directory=tmp_path)) 

 

# if the socket file doesn't exist, spin up the daemon process 

lock_fd = os.open(lock_path, os.O_RDWR | os.O_CREAT, 0o600) 

fcntl.lockf(lock_fd, fcntl.LOCK_EX) 

 

if not os.path.exists(socket_path): 

messages.append('local domain socket does not exist, starting it') 

original_path = os.getcwd() 

r, w = os.pipe() 

pid = fork_process() 

 

if pid == 0: 

try: 

os.close(r) 

wfd = os.fdopen(w, 'w') 

process = ConnectionProcess(wfd, play_context, socket_path, original_path, ansible_playbook_pid) 

process.start() 

except Exception: 

messages.append(traceback.format_exc()) 

rc = 1 

 

fcntl.lockf(lock_fd, fcntl.LOCK_UN) 

os.close(lock_fd) 

 

238 ↛ 241line 238 didn't jump to line 241, because the condition on line 238 was never false if rc == 0: 

process.run() 

 

sys.exit(rc) 

 

else: 

os.close(w) 

rfd = os.fdopen(r, 'r') 

data = json.loads(rfd.read()) 

messages.extend(data.pop('messages')) 

result.update(data) 

 

else: 

messages.append('found existing local domain socket, using it!') 

conn = Connection(socket_path) 

pc_data = to_text(init_data) 

try: 

messages.extend(conn.update_play_context(pc_data)) 

except Exception as exc: 

# Only network_cli has update_play context, so missing this is 

# not fatal e.g. netconf 

if isinstance(exc, ConnectionError) and getattr(exc, 'code', None) == -32601: 

pass 

else: 

result.update({ 

'error': to_text(exc), 

'exception': traceback.format_exc() 

}) 

 

messages.append(sys.stdout.getvalue()) 

result.update({ 

'messages': messages, 

'socket_path': socket_path 

}) 

 

sys.stdout = saved_stdout 

274 ↛ 275line 274 didn't jump to line 275, because the condition on line 274 was never true if 'exception' in result: 

rc = 1 

sys.stderr.write(json.dumps(result)) 

else: 

rc = 0 

sys.stdout.write(json.dumps(result)) 

 

sys.exit(rc) 

 

 

284 ↛ exitline 284 didn't exit the module, because the condition on line 284 was never falseif __name__ == '__main__': 

display = Display() 

main()