cleepcli 1.43.10__tar.gz → 1.44.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. {cleepcli-1.43.10/cleepcli.egg-info → cleepcli-1.44.0}/PKG-INFO +1 -1
  2. {cleepcli-1.43.10 → cleepcli-1.44.0}/bin/ccli +19 -13
  3. {cleepcli-1.43.10 → cleepcli-1.44.0}/bin/cleep-cli +19 -13
  4. {cleepcli-1.43.10 → cleepcli-1.44.0}/cleepcli/ci.py +3 -1
  5. {cleepcli-1.43.10 → cleepcli-1.44.0}/cleepcli/cleepapi.py +158 -6
  6. {cleepcli-1.43.10 → cleepcli-1.44.0}/cleepcli/console.py +38 -48
  7. {cleepcli-1.43.10 → cleepcli-1.44.0}/cleepcli/file.py +10 -8
  8. {cleepcli-1.43.10 → cleepcli-1.44.0}/cleepcli/git.py +2 -2
  9. {cleepcli-1.43.10 → cleepcli-1.44.0}/cleepcli/module.py +1 -1
  10. {cleepcli-1.43.10 → cleepcli-1.44.0}/cleepcli/version.py +1 -1
  11. cleepcli-1.44.0/cleepcli/watch.py +481 -0
  12. {cleepcli-1.43.10 → cleepcli-1.44.0/cleepcli.egg-info}/PKG-INFO +1 -1
  13. {cleepcli-1.43.10 → cleepcli-1.44.0}/cleepcli.egg-info/SOURCES.txt +3 -1
  14. cleepcli-1.44.0/tests/test_console.py +51 -0
  15. cleepcli-1.44.0/tests/test_watch.py +208 -0
  16. cleepcli-1.43.10/cleepcli/watch.py +0 -375
  17. {cleepcli-1.43.10 → cleepcli-1.44.0}/LICENSE +0 -0
  18. {cleepcli-1.43.10 → cleepcli-1.44.0}/MANIFEST.in +0 -0
  19. {cleepcli-1.43.10 → cleepcli-1.44.0}/README.md +0 -0
  20. {cleepcli-1.43.10 → cleepcli-1.44.0}/cleepcli/__init__.py +0 -0
  21. {cleepcli-1.43.10 → cleepcli-1.44.0}/cleepcli/check.py +0 -0
  22. {cleepcli-1.43.10 → cleepcli-1.44.0}/cleepcli/config.py +0 -0
  23. {cleepcli-1.43.10 → cleepcli-1.44.0}/cleepcli/docs.py +0 -0
  24. {cleepcli-1.43.10 → cleepcli-1.44.0}/cleepcli/package.py +0 -0
  25. {cleepcli-1.43.10 → cleepcli-1.44.0}/cleepcli/test.py +0 -0
  26. {cleepcli-1.43.10 → cleepcli-1.44.0}/cleepcli/tools.py +0 -0
  27. {cleepcli-1.43.10 → cleepcli-1.44.0}/cleepcli.egg-info/dependency_links.txt +0 -0
  28. {cleepcli-1.43.10 → cleepcli-1.44.0}/cleepcli.egg-info/requires.txt +0 -0
  29. {cleepcli-1.43.10 → cleepcli-1.44.0}/cleepcli.egg-info/top_level.txt +0 -0
  30. {cleepcli-1.43.10 → cleepcli-1.44.0}/pyproject.toml +0 -0
  31. {cleepcli-1.43.10 → cleepcli-1.44.0}/requirements.txt +0 -0
  32. {cleepcli-1.43.10 → cleepcli-1.44.0}/setup.cfg +0 -0
  33. {cleepcli-1.43.10 → cleepcli-1.44.0}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cleepcli
3
- Version: 1.43.10
3
+ Version: 1.44.0
4
4
  Summary: Cleep-cli helps developers to build great Cleep applications from command line.
5
5
  Home-page: https://github.com/CleepDevice/cleep-cli
6
6
  Author: Tanguy Bonneau
@@ -90,7 +90,8 @@ def coreget():
90
90
 
91
91
  # force coresync to synchronize installation with repo
92
92
  f = File()
93
- res = f.core_sync()
93
+ if not f.core_sync():
94
+ sys.exit(1)
94
95
 
95
96
  # core modules
96
97
  if not get_core_mods():
@@ -293,9 +294,10 @@ def modssync(run_scripts):
293
294
  res = f.module_sync(module)
294
295
  if not res:
295
296
  sys.exit(1)
296
- res = f.module_run_install_scripts(module)
297
- if not res:
298
- sys.exit(1)
297
+ if run_scripts:
298
+ res = f.module_run_install_scripts(module)
299
+ if not res:
300
+ sys.exit(1)
299
301
 
300
302
  logging.info('Done')
301
303
 
@@ -899,17 +901,21 @@ def cli():
899
901
 
900
902
 
901
903
  if __name__ == '__main__':
902
- def is_raspbian():
903
- if platform.system()!='Linux':
904
+ def is_raspberry_pi_os():
905
+ if platform.system() != 'Linux':
904
906
  return False
905
- res = subprocess.Popen(u'cat /etc/os-release | grep -i raspbian | wc -l', stdout=subprocess.PIPE, shell=True)
906
- stdout = res.communicate()[0]
907
- if stdout.strip() == '0':
907
+ try:
908
+ with open('/etc/os-release', 'r', encoding='utf-8') as handle:
909
+ content = handle.read().lower()
910
+ except OSError:
908
911
  return False
909
- return True
912
+ return any(
913
+ token in content for token in ('raspbian', 'raspios', 'raspberry')
914
+ )
910
915
 
911
- # execute only on raspbian
912
- if is_raspbian():
916
+ # execute only on Raspberry Pi OS
917
+ if is_raspberry_pi_os():
913
918
  cli()
914
919
  else:
915
- click.echo('Cleep-cli runs only on RaspberrypiOS distribution')
920
+ click.echo('Cleep-cli runs only on Raspberry Pi OS')
921
+ sys.exit(1)
@@ -90,7 +90,8 @@ def coreget():
90
90
 
91
91
  # force coresync to synchronize installation with repo
92
92
  f = File()
93
- res = f.core_sync()
93
+ if not f.core_sync():
94
+ sys.exit(1)
94
95
 
95
96
  # core modules
96
97
  if not get_core_mods():
@@ -293,9 +294,10 @@ def modssync(run_scripts):
293
294
  res = f.module_sync(module)
294
295
  if not res:
295
296
  sys.exit(1)
296
- res = f.module_run_install_scripts(module)
297
- if not res:
298
- sys.exit(1)
297
+ if run_scripts:
298
+ res = f.module_run_install_scripts(module)
299
+ if not res:
300
+ sys.exit(1)
299
301
 
300
302
  logging.info('Done')
301
303
 
@@ -899,17 +901,21 @@ def cli():
899
901
 
900
902
 
901
903
  if __name__ == '__main__':
902
- def is_raspbian():
903
- if platform.system()!='Linux':
904
+ def is_raspberry_pi_os():
905
+ if platform.system() != 'Linux':
904
906
  return False
905
- res = subprocess.Popen(u'cat /etc/os-release | grep -i raspbian | wc -l', stdout=subprocess.PIPE, shell=True)
906
- stdout = res.communicate()[0]
907
- if stdout.strip() == '0':
907
+ try:
908
+ with open('/etc/os-release', 'r', encoding='utf-8') as handle:
909
+ content = handle.read().lower()
910
+ except OSError:
908
911
  return False
909
- return True
912
+ return any(
913
+ token in content for token in ('raspbian', 'raspios', 'raspberry')
914
+ )
910
915
 
911
- # execute only on raspbian
912
- if is_raspbian():
916
+ # execute only on Raspberry Pi OS
917
+ if is_raspberry_pi_os():
913
918
  cli()
914
919
  else:
915
- click.echo('Cleep-cli runs only on RaspberrypiOS distribution')
920
+ click.echo('Cleep-cli runs only on Raspberry Pi OS')
921
+ sys.exit(1)
@@ -12,7 +12,7 @@ import urllib.parse
12
12
  from . import config
13
13
  from .console import Console
14
14
  from .check import Check
15
- from .cleepapi import resolve_rpc_url
15
+ from .cleepapi import local_socket_available, request_over_local_socket, resolve_rpc_url
16
16
  import subprocess
17
17
 
18
18
  requests.packages.urllib3.disable_warnings()
@@ -284,6 +284,8 @@ class Ci:
284
284
  """
285
285
  POST JSON to Cleep /command (self-signed TLS accepted).
286
286
  """
287
+ if local_socket_available():
288
+ return request_over_local_socket('POST', self.command_url, json_body=payload)
287
289
  return requests.post(self.command_url, json=payload, verify=False)
288
290
 
289
291
  def _rpc_get(self, url):
@@ -17,6 +17,9 @@ DEFAULT_RPC_URLS = (
17
17
  'https://127.0.0.1:443',
18
18
  'http://127.0.0.1:80',
19
19
  )
20
+ # Local commands (frontend restart, docs, CI) use this socket so they
21
+ # stay available when TCP requires the Cleep password.
22
+ LOCAL_SOCKET_PATH = '/run/cleep/rpc.sock'
20
23
 
21
24
 
22
25
  def resolve_rpc_url(urls=None, timeout=3.0):
@@ -54,6 +57,144 @@ def resolve_rpc_url(urls=None, timeout=3.0):
54
57
  raise Exception('Unable to reach Cleep RPC (tried %s): %s' % (', '.join(candidates), last_error))
55
58
 
56
59
 
60
+ def local_socket_available(path=None):
61
+ """
62
+ Return True when the local Cleep RPC socket exists.
63
+ """
64
+ return os.path.exists(path or LOCAL_SOCKET_PATH)
65
+
66
+
67
+ def _http_path(url):
68
+ """
69
+ Return the path and query of an RPC URL.
70
+ """
71
+ parts = urllib.parse.urlsplit(url)
72
+ path = parts.path or '/'
73
+ if parts.query:
74
+ path += '?' + parts.query
75
+ return path
76
+
77
+
78
+ def _read_http_response(sock):
79
+ """
80
+ Read one HTTP/1.1 response from a socket.
81
+
82
+ Returns:
83
+ tuple: status code (int), body (bytes)
84
+ """
85
+ data = b''
86
+ while b'\r\n\r\n' not in data:
87
+ chunk = sock.recv(4096)
88
+ if not chunk:
89
+ break
90
+ data += chunk
91
+ if b'\r\n\r\n' not in data:
92
+ raise Exception('Incomplete HTTP response from local socket')
93
+
94
+ header_blob, rest = data.split(b'\r\n\r\n', 1)
95
+ lines = header_blob.split(b'\r\n')
96
+ status_code = int(lines[0].split()[1])
97
+ headers = {}
98
+ for line in lines[1:]:
99
+ if b':' not in line:
100
+ continue
101
+ name, value = line.split(b':', 1)
102
+ headers[name.decode('ascii').lower()] = value.strip().decode('ascii')
103
+
104
+ if headers.get('transfer-encoding', '').lower() == 'chunked':
105
+ body = _read_chunked(sock, rest)
106
+ elif 'content-length' in headers:
107
+ length = int(headers['content-length'])
108
+ while len(rest) < length:
109
+ chunk = sock.recv(4096)
110
+ if not chunk:
111
+ break
112
+ rest += chunk
113
+ body = rest[:length]
114
+ else:
115
+ while True:
116
+ chunk = sock.recv(4096)
117
+ if not chunk:
118
+ break
119
+ rest += chunk
120
+ body = rest
121
+ return status_code, body
122
+
123
+
124
+ def _read_chunked(sock, pending):
125
+ """
126
+ Decode a chunked HTTP body.
127
+ """
128
+ data = pending
129
+ body = b''
130
+ while True:
131
+ while b'\r\n' not in data:
132
+ chunk = sock.recv(4096)
133
+ if not chunk:
134
+ return body
135
+ data += chunk
136
+ size_line, data = data.split(b'\r\n', 1)
137
+ size = int(size_line.split(b';', 1)[0], 16)
138
+ if size == 0:
139
+ return body
140
+ while len(data) < size + 2:
141
+ chunk = sock.recv(4096)
142
+ if not chunk:
143
+ return body
144
+ data += chunk
145
+ body += data[:size]
146
+ data = data[size + 2:]
147
+
148
+
149
+ def request_over_local_socket(method, url, json_body=None, timeout=30.0, path=None):
150
+ """
151
+ Send one HTTP request through the local Cleep Unix socket.
152
+
153
+ Args:
154
+ method (str): HTTP method
155
+ url (str): request URL, used for the path when path is omitted
156
+ json_body (dict): JSON body for POST
157
+ timeout (float): socket timeout in seconds
158
+ path (str): socket path
159
+
160
+ Returns:
161
+ requests.Response
162
+ """
163
+ import socket
164
+ from requests import Response
165
+
166
+ socket_path = path or LOCAL_SOCKET_PATH
167
+ http_path = _http_path(url)
168
+ body = b''
169
+ headers = [
170
+ '%s %s HTTP/1.1' % (method, http_path),
171
+ 'Host: localhost',
172
+ 'Accept: application/json',
173
+ 'Connection: close',
174
+ ]
175
+ if json_body is not None:
176
+ body = json.dumps(json_body).encode('utf-8')
177
+ headers.append('Content-Type: application/json')
178
+ headers.append('Content-Length: %s' % len(body))
179
+ raw = ('\r\n'.join(headers) + '\r\n\r\n').encode('ascii') + body
180
+
181
+ sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
182
+ sock.settimeout(timeout)
183
+ try:
184
+ sock.connect(socket_path)
185
+ sock.sendall(raw)
186
+ status_code, payload = _read_http_response(sock)
187
+ finally:
188
+ sock.close()
189
+
190
+ response = Response()
191
+ response.status_code = status_code
192
+ response._content = payload
193
+ response.url = 'http://localhost' + http_path
194
+ response.encoding = 'utf-8'
195
+ return response
196
+
197
+
57
198
  class CleepApi():
58
199
  """
59
200
  Cleep api helper
@@ -81,9 +222,10 @@ class CleepApi():
81
222
 
82
223
  cmd = '/bin/systemctl restart cleep'
83
224
  c = Console()
84
- resp = c.command(cmd)
225
+ # Maximum wait. systemctl returns as soon as the service has restarted.
226
+ resp = c.command(cmd, timeout=60)
85
227
  self.logger.debug('Systemctl resp: %s' % resp)
86
- if resp['error'] or resp['killed']:
228
+ if resp['killed'] or resp['returncode'] != 0:
87
229
  self.logger.error('Error restarting cleep backend')
88
230
  return False
89
231
 
@@ -95,7 +237,11 @@ class CleepApi():
95
237
  """
96
238
  self.logger.info('Restarting frontend')
97
239
  data = {'to':'developer', 'command':'restart_frontend'}
98
- self.__post(self.command_url, data)
240
+ result = self.__post(self.command_url, data)
241
+ if not result or result[0] != 200:
242
+ self.logger.error('Error restarting cleep frontend')
243
+ return False
244
+ return True
99
245
 
100
246
  def get_documentation(self, module_name):
101
247
  """
@@ -132,7 +278,7 @@ class CleepApi():
132
278
  (status_code, resp) = self.__get(url)
133
279
 
134
280
  if status_code != 200:
135
- raise Exception("Unable to call cleep %s endpoint", url)
281
+ raise Exception("Unable to call cleep %s endpoint" % url)
136
282
  return resp
137
283
 
138
284
  def __post(self, url, data):
@@ -152,7 +298,10 @@ class CleepApi():
152
298
  """
153
299
  try:
154
300
  self.logger.debug("POST url: %s", url)
155
- resp = requests.post(url, json=data, verify=False)
301
+ if local_socket_available():
302
+ resp = request_over_local_socket('POST', url, json_body=data)
303
+ else:
304
+ resp = requests.post(url, json=data, verify=False)
156
305
  resp_data = resp.json()
157
306
  self.logger.debug('Response[%s]: %s', resp.status_code, resp_data)
158
307
  return (resp.status_code, resp_data)
@@ -178,7 +327,10 @@ class CleepApi():
178
327
  """
179
328
  try:
180
329
  self.logger.debug("GET url: %s", url)
181
- resp = requests.get(url, verify=False)
330
+ if local_socket_available():
331
+ resp = request_over_local_socket('GET', url)
332
+ else:
333
+ resp = requests.get(url, verify=False)
182
334
  resp_data = resp.json()
183
335
  self.logger.debug('Response[%s]: %s', resp.status_code, resp_data)
184
336
  return (resp.status_code, resp_data)
@@ -14,6 +14,9 @@ import signal
14
14
  import logging
15
15
  import re
16
16
 
17
+ ON_POSIX = os.name == "posix"
18
+
19
+
17
20
  class EndlessConsole(Thread):
18
21
  """
19
22
  Helper class to execute long command line (system update...)
@@ -322,65 +325,52 @@ class Console():
322
325
  stdout=subprocess.PIPE,
323
326
  stderr=subprocess.PIPE,
324
327
  close_fds=True,
325
- preexec_fn=os.setsid
328
+ preexec_fn=os.setsid if ON_POSIX else None,
326
329
  )
327
330
  pid = proc.pid
328
-
329
- # wait for end of command line
330
- done = False
331
- start = time.time()
332
331
  killed = False
333
- return_code = None
334
- while not done:
335
- # read stds to avoid deadlocks
336
- (stdout, stderr) = proc.communicate()
337
- if stdout:
338
- result['stdout'] += self.__process_lines(stdout.split(b'\n'))
339
- if stderr:
340
- result['stderr'] += self.__process_lines(stderr.split(b'\n'))
341
-
342
- # check if command has finished
343
- proc.poll()
344
- if proc.returncode is not None:
345
- # command terminated
346
- self.logger.trace('Command terminated with returncode %s' % proc.returncode)
347
- return_code = proc.returncode
348
- self.last_return_code = return_code
349
- done = True
350
- break
351
-
352
- # check timeout
353
- if time.time() > (start + timeout):
354
- # timeout is over, kill command
355
- pgid = os.getpgid(pid)
356
- self.logger.debug('Timeout over, kill command for PID=%s PGID=%s' % (pid, pgid))
357
- try:
358
- if ON_POSIX:
359
- os.killpg(pgid, signal.SIGKILL)
360
- else: # pragma: no cover
361
- proc.kill()
362
- except Exception: # pragma: no cover
363
- pass
364
- killed = True
365
- break
332
+ stdout = b""
333
+ stderr = b""
366
334
 
367
- # pause
368
- time.sleep(0.125)
369
-
370
- # prepare result
371
- result['returncode'] = return_code
372
- if not killed:
373
- result['error'] = len(result['stderr']) > 0
374
- self.logger.trace('Result: %s' % result)
335
+ try:
336
+ stdout, stderr = proc.communicate(timeout=timeout)
337
+ except subprocess.TimeoutExpired:
338
+ self.logger.debug("Timeout over, kill command for PID=%s" % pid)
339
+ try:
340
+ if ON_POSIX:
341
+ os.killpg(os.getpgid(pid), signal.SIGKILL)
342
+ else: # pragma: no cover
343
+ proc.kill()
344
+ except Exception: # pragma: no cover
345
+ pass
346
+ killed = True
347
+ try:
348
+ stdout, stderr = proc.communicate(timeout=1.0)
349
+ except Exception: # pragma: no cover
350
+ stdout, stderr = b"", b""
351
+
352
+ if stdout:
353
+ result["stdout"] = self.__process_lines(stdout.split(b"\n"))
354
+ if stderr:
355
+ result["stderr"] = self.__process_lines(stderr.split(b"\n"))
356
+
357
+ return_code = proc.returncode
358
+ self.last_return_code = return_code
359
+ result["returncode"] = return_code
360
+ result["killed"] = killed
361
+ # Prefer return code over stderr: tools like systemctl often write to stderr on success
362
+ result["error"] = killed or return_code not in (0, None)
363
+ self.logger.trace("Command terminated with returncode %s" % return_code)
364
+ self.logger.trace("Result: %s" % result)
375
365
 
376
366
  # make sure all stds are closed
377
367
  try:
378
368
  proc.stdout.close()
379
- except Exception: # pragma: no cover
369
+ except Exception: # pragma: no cover
380
370
  pass
381
371
  try:
382
372
  proc.stderr.close()
383
- except Exception: # pragma: no cover
373
+ except Exception: # pragma: no cover
384
374
  pass
385
375
 
386
376
  # trigger callback (used for delayed command)
@@ -32,6 +32,7 @@ class File():
32
32
  'CONFIG_DIR': config.CONFIG_DIR,
33
33
  }
34
34
  cmd = """
35
+ set -e
35
36
  mkdir -p "%(HTML_DST)s/"
36
37
  mkdir -p "%(CORE_DST)s/modules"
37
38
  mkdir -p "%(MEDIA_DST)s/modules"
@@ -42,11 +43,11 @@ rsync -av "%(REPO_DIR)s/bin/cleep" "%(BIN_DST)s/cleep"
42
43
  rsync -av "%(REPO_DIR)s/medias/sounds" "%(MEDIA_DST)s/sounds/" --delete
43
44
  """ % variables
44
45
  self.logger.debug('Coresync cmd: %s' % cmd)
45
- resp = c.command(cmd, 15)
46
+ resp = c.command(cmd, 120)
46
47
  self.logger.debug('Coresync stdout:\n%s' % '\n'.join(resp['stdout']))
47
48
  if resp['stderr']:
48
49
  self.logger.debug('Coresync stderr:\n%s' % '\n'.join(resp['stderr']))
49
- if resp['error'] or resp['killed']:
50
+ if resp['killed'] or resp['returncode'] != 0:
50
51
  self.logger.error('Error occured while sync core content: %s' % ('killed' if resp['killed'] else resp['stderr']))
51
52
  return False
52
53
 
@@ -71,7 +72,8 @@ rsync -av "%(REPO_DIR)s/medias/sounds" "%(MEDIA_DST)s/sounds/" --delete
71
72
  mod_scripts_dst = os.path.join(config.MODULES_SCRIPTS_DST, module_name)
72
73
 
73
74
  c = Console()
74
- cmd = """
75
+ cmd = """
76
+ set -e
75
77
  if [ -d "%(BACKEND_SRC)s" ]; then
76
78
  /bin/mkdir -p "%(BACKEND_DST)s"
77
79
  rsync -av "%(BACKEND_SRC)s" "%(BACKEND_DST)s" --delete --exclude "*.pyc" --exclude "*__pycache__*"
@@ -90,13 +92,13 @@ fi
90
92
  'SCRIPTS_SRC': mod_scripts_src, 'SCRIPTS_DST': mod_scripts_dst
91
93
  }
92
94
  self.logger.debug('Modsync cmd: %s' % cmd)
93
- resp = c.command(cmd, 15)
95
+ resp = c.command(cmd, 120)
94
96
 
95
97
  self.logger.debug('Modsync stdout:\n%s' % '\n'.join(resp['stdout']))
96
98
  if resp['stderr']:
97
99
  self.logger.debug('Modsync stderr:\n%s' % '\n'.join(resp['stderr']))
98
- if resp['error'] or resp[u'killed']:
99
- self.logger.error(u'Error occured while sync module content: %s' % (u'killed' if resp[u'killed'] else resp[u'stderr']))
100
+ if resp['killed'] or resp['returncode'] != 0:
101
+ self.logger.error('Error occured while sync module content: %s' % ('killed' if resp['killed'] else resp['stderr']))
100
102
  return False
101
103
 
102
104
  return True
@@ -116,14 +118,14 @@ fi
116
118
  if os.path.exists(preinst_script):
117
119
  self.logger.info('Running %s scripts', preinst_script)
118
120
  resp = console.command(preinst_script, timeout=300)
119
- if resp["returncode"] != 0:
121
+ if resp["killed"] or resp["returncode"] != 0:
120
122
  self.logger.error('Error running %s [%s]: %s', preinst_script, resp["returncode"], ''.join(resp["stderr"]))
121
123
  return False
122
124
 
123
125
  if os.path.exists(postinst_script):
124
126
  self.logger.info('Running %s scripts', postinst_script)
125
127
  resp = console.command(postinst_script, timeout=300)
126
- if resp["returncode"] != 0:
128
+ if resp["killed"] or resp["returncode"] != 0:
127
129
  self.logger.error('Error running %s [%s]: %s', postinst_script, resp["returncode"], ''.join(resp["stderr"]))
128
130
  return False
129
131
 
@@ -25,7 +25,7 @@ class Git():
25
25
  self.logger.debug('cmd: %s' % cmd)
26
26
  resp = c.command(cmd, 60)
27
27
  self.logger.debug('Pull resp: %s' % resp)
28
- if resp['error'] or resp['killed']:
28
+ if resp['killed'] or resp['returncode'] != 0:
29
29
  self.logger.error('Error occured while pulling repository: %s' % ('killed' if resp['killed'] else resp['stderr']))
30
30
  return False
31
31
 
@@ -47,7 +47,7 @@ class Git():
47
47
  self.logger.debug('cmd: %s' % cmd)
48
48
  resp = c.command(cmd, 60)
49
49
  self.logger.debug('Clone resp: %s' % resp)
50
- if resp['error'] or resp['killed']:
50
+ if resp['killed'] or resp['returncode'] != 0:
51
51
  self.logger.error('Error occured while cloning repository: %s' % ('killed' if resp['killed'] else resp['stderr']))
52
52
  return False
53
53
 
@@ -645,7 +645,7 @@ a:hover.icon.icon-home {
645
645
  'CHANGELOG': templates['CHANGELOG'],
646
646
  'GIT_IGNORE': templates['GIT_IGNORE'],
647
647
  }, 10)
648
- if resp['error'] or resp['killed']:
648
+ if resp['killed'] or resp['returncode'] != 0:
649
649
  self.logger.error('Error occured while creating application: %s' % ('killed' if resp['killed'] else resp['stderr']))
650
650
  shutil.rmtree(path)
651
651
  return False
@@ -1,4 +1,4 @@
1
1
  #!/usr/bin/env python
2
2
  # -*- coding: utf-8 -*-
3
3
 
4
- VERSION = "1.43.10"
4
+ VERSION = "1.44.0"