cleepcli 1.43.4__tar.gz → 1.43.7__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 (31) hide show
  1. cleepcli-1.43.7/PKG-INFO +30 -0
  2. {cleepcli-1.43.4 → cleepcli-1.43.7}/README.md +52 -29
  3. cleepcli-1.43.7/cleepcli/ci.py +397 -0
  4. {cleepcli-1.43.4 → cleepcli-1.43.7}/cleepcli/cleepapi.py +48 -1
  5. {cleepcli-1.43.4 → cleepcli-1.43.7}/cleepcli/docs.py +210 -115
  6. {cleepcli-1.43.4 → cleepcli-1.43.7}/cleepcli/module.py +4 -3
  7. {cleepcli-1.43.4 → cleepcli-1.43.7}/cleepcli/package.py +158 -102
  8. {cleepcli-1.43.4 → cleepcli-1.43.7}/cleepcli/test.py +2 -2
  9. {cleepcli-1.43.4 → cleepcli-1.43.7}/cleepcli/version.py +1 -2
  10. cleepcli-1.43.7/cleepcli.egg-info/PKG-INFO +30 -0
  11. cleepcli-1.43.4/PKG-INFO +0 -15
  12. cleepcli-1.43.4/cleepcli/ci.py +0 -301
  13. cleepcli-1.43.4/cleepcli.egg-info/PKG-INFO +0 -15
  14. {cleepcli-1.43.4 → cleepcli-1.43.7}/LICENSE +0 -0
  15. {cleepcli-1.43.4 → cleepcli-1.43.7}/MANIFEST.in +0 -0
  16. {cleepcli-1.43.4 → cleepcli-1.43.7}/bin/ccli +0 -0
  17. {cleepcli-1.43.4 → cleepcli-1.43.7}/bin/cleep-cli +0 -0
  18. {cleepcli-1.43.4 → cleepcli-1.43.7}/cleepcli/__init__.py +0 -0
  19. {cleepcli-1.43.4 → cleepcli-1.43.7}/cleepcli/check.py +0 -0
  20. {cleepcli-1.43.4 → cleepcli-1.43.7}/cleepcli/config.py +0 -0
  21. {cleepcli-1.43.4 → cleepcli-1.43.7}/cleepcli/console.py +0 -0
  22. {cleepcli-1.43.4 → cleepcli-1.43.7}/cleepcli/file.py +0 -0
  23. {cleepcli-1.43.4 → cleepcli-1.43.7}/cleepcli/git.py +0 -0
  24. {cleepcli-1.43.4 → cleepcli-1.43.7}/cleepcli/tools.py +0 -0
  25. {cleepcli-1.43.4 → cleepcli-1.43.7}/cleepcli/watch.py +0 -0
  26. {cleepcli-1.43.4 → cleepcli-1.43.7}/cleepcli.egg-info/SOURCES.txt +0 -0
  27. {cleepcli-1.43.4 → cleepcli-1.43.7}/cleepcli.egg-info/dependency_links.txt +0 -0
  28. {cleepcli-1.43.4 → cleepcli-1.43.7}/cleepcli.egg-info/requires.txt +0 -0
  29. {cleepcli-1.43.4 → cleepcli-1.43.7}/cleepcli.egg-info/top_level.txt +0 -0
  30. {cleepcli-1.43.4 → cleepcli-1.43.7}/setup.cfg +0 -0
  31. {cleepcli-1.43.4 → cleepcli-1.43.7}/setup.py +0 -0
@@ -0,0 +1,30 @@
1
+ Metadata-Version: 2.4
2
+ Name: cleepcli
3
+ Version: 1.43.7
4
+ Summary: Cleep-cli helps developers to build great Cleep applications from command line.
5
+ Home-page: https://github.com/CleepDevice/cleep-cli
6
+ Author: Tanguy Bonneau
7
+ Author-email: tanguy.bonneau@gmail.com
8
+ Maintainer: Tanguy Bonneau
9
+ Maintainer-email: tanguy.bonneau@gmail.com
10
+ License-File: LICENSE
11
+ Requires-Dist: Click<9.0,>=8.0
12
+ Requires-Dist: watchdog<4.0.0,>=3.0.0
13
+ Requires-Dist: Sphinx<6.0.0,>=5.0.0
14
+ Requires-Dist: sphinx-rtd-theme>=1.0.0
15
+ Requires-Dist: CProfileV>=1.0.7
16
+ Requires-Dist: PyGithub==1.54.1
17
+ Requires-Dist: pylint<3.0.0,>=2.11.1
18
+ Requires-Dist: semver==3.0.0
19
+ Requires-Dist: coverage==7.2.7
20
+ Requires-Dist: passlib==1.7.4
21
+ Requires-Dist: requests==2.31.0
22
+ Requires-Dist: psutil==5.9.4
23
+ Dynamic: author
24
+ Dynamic: author-email
25
+ Dynamic: home-page
26
+ Dynamic: license-file
27
+ Dynamic: maintainer
28
+ Dynamic: maintainer-email
29
+ Dynamic: requires-dist
30
+ Dynamic: summary
@@ -1,60 +1,70 @@
1
1
  # Cleep-cli
2
2
 
3
3
  This utility helps developers to build Cleep applications providing some useful commands:
4
- * Core commands:
5
- * `coreget` to clone or pull official Cleep repository.
6
- * `coredocs` to generate core documentation in HTML format.
7
- * `coresync` to synchronize sources from repository to execution folders.
8
- * `coretests` to perform core tests suite.
9
- * `coretestscov` to display last core tests code coverage report.
10
- * `cpuprof` to run CPU profiler on application execution instance.
11
- * `memprof` to run memory profiler on application execution instance.
12
- * `reset` clear existing installed Cleep files and install it again.
13
- * Module commands:
14
- * `modcreate` to create module (aka application) skeleton.
15
- * `moddelete` to delete installed files of a module.
16
- * `moddocs` to generate module documentation.
17
- * `modsync` to synchronize sources from module folder to execution folders.
18
- * `modtests` to execute module tests.
19
- * `modtestscov` to display last module tests code coverage report.
20
- * `modbuild` to create application package (.zip)
21
- * `modcheckback` to check app backend (python files structure and content)
22
- * `modcheckfront` to check app frontend (js files and desc.json)
23
- * `modchecktests` to check app tests files
24
- * `modcheckscripts` to check app scripts files
25
- * `modcheckcode` to check backend code quality (run pylint)
26
- * Watch commands:
27
- * `watch` to watch for repository filesystem changes and automatically update files on execution folders. It also restart backend or/and frontend according to changes.
28
- * Misc commands:
29
- * `version` to get cleep-cli version
4
+
5
+ - Core commands:
6
+ - `coreget` to clone or pull official Cleep repository.
7
+ - `coredocs` to generate core documentation in HTML format.
8
+ - `coresync` to synchronize sources from repository to execution folders.
9
+ - `coretests` to perform core tests suite.
10
+ - `coretestscov` to display last core tests code coverage report.
11
+ - `cpuprof` to run CPU profiler on application execution instance.
12
+ - `memprof` to run memory profiler on application execution instance.
13
+ - `reset` clear existing installed Cleep files and install it again.
14
+ - Module commands:
15
+ - `modcreate` to create module (aka application) skeleton.
16
+ - `moddelete` to delete installed files of a module.
17
+ - `moddocs` to generate module documentation.
18
+ - `modsync` to synchronize sources from module folder to execution folders.
19
+ - `modtests` to execute module tests.
20
+ - `modtestscov` to display last module tests code coverage report.
21
+ - `modbuild` to create application package (.zip)
22
+ - `modcheckback` to check app backend (python files structure and content)
23
+ - `modcheckfront` to check app frontend (js files and desc.json)
24
+ - `modchecktests` to check app tests files
25
+ - `modcheckscripts` to check app scripts files
26
+ - `modcheckcode` to check backend code quality (run pylint)
27
+ - Watch commands:
28
+ - `watch` to watch for repository filesystem changes and automatically update files on execution folders. It also restart backend or/and frontend according to changes.
29
+ - Misc commands:
30
+ - `version` to get cleep-cli version
30
31
 
31
32
  ## Installation
33
+
32
34
  Cleep-cli is automatically installed and managed by [Cleep developer](https://github.com/tangb/cleepmod-developer) application.
33
35
 
34
36
  If you want to install it manually, execute following command:
37
+
35
38
  > pip install cleepcli
36
39
 
37
40
  ## Compatibility
41
+
38
42
  This cli is written in Python and is supposed to be used only on Raspbian platform for Cleep application development because it has harcoded paths.
39
43
 
40
44
  ## Help
45
+
41
46
  Execute this command to get help on this cli:
47
+
42
48
  > cleep-cli --help
43
49
 
44
50
  ## Watch usage
51
+
45
52
  Launch `watch` cli command to monitor changes on "/root/cleep-dev" directory after getting official Cleep repository using `coreget` command.
46
53
 
47
54
  Then open your favorite editor on your development computer, configure a remote sync plugin to push local changes on your raspberry pi then cleep-cli will synchronize sources on Cleep execution environment and restart application automatically.
48
55
 
49
56
  ### Paths
57
+
50
58
  The cli clone repository on `/root/cleep-dev` folder. Modules are in `/root/cleep-dev/modules` folder.
51
59
  So if you develop on Cleep core, your editor must sync to `/root/cleep-dev` remote directory while if you're developping only on a module your editor must sync to `/root/cleep-dev/modules/<your module name>` remote directory.
52
60
 
53
61
  ### Visual studio code
62
+
54
63
  Install Visual studio code [sftp](https://marketplace.visualstudio.com/items?itemName=Natizyskunk.sftp) plugin and configure it to access your raspberry pi:
55
- * Open VSCode command palette pressing CTRL-SHIFT-P
56
- * Type "sftp: config"
57
- * Fill opened file with:
64
+
65
+ - Open VSCode command palette pressing CTRL-SHIFT-P
66
+ - Type "sftp: config"
67
+ - Fill opened file with:
58
68
 
59
69
  ```
60
70
  {
@@ -81,9 +91,11 @@ Install Visual studio code [sftp](https://marketplace.visualstudio.com/items?ite
81
91
  ```
82
92
 
83
93
  ### Local developments
94
+
84
95
  If you want to develop directly on the raspberry using vim or nano, simply develop on `/root/cleep-dev/` directory. Cleep-cli watcher will synchronize all your modifications.
85
96
 
86
97
  ## How it works
98
+
87
99
  This cli executes git commands to clone or update repository.
88
100
 
89
101
  It uses rsync commands to synchronize files.
@@ -92,3 +104,14 @@ It uses pylint to check code quality (it returns a score)
92
104
 
93
105
  The [watchdog](https://pypi.org/project/watchdog/) python library is used to detect changes on filesystem. According to changed files, it detects if change occurs on frontend or backend files and send commands to restart Cleep application.
94
106
 
107
+ ## CI (GitHub Actions)
108
+
109
+ A tag `vX.Y.Z` (or a manual **workflow_dispatch**) builds a **universal** wheel + sdist on `ubuntu-latest` (Python 3.9) and uploads them to PyPI.
110
+
111
+ GitHub **environment** `actions` (Settings → Environments), secret:
112
+
113
+ | Secret | Description |
114
+ | ---------------- | ----------------------------------------------------------- |
115
+ | `PYPI_API_TOKEN` | PyPI API token (`pypi-...`). Username Twine is `__token__`. |
116
+
117
+ No Raspberry Pi / Woodpecker needed: the package is pure Python (`py2.py3-none-any`).
@@ -0,0 +1,397 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+
4
+ import logging
5
+ import zipfile
6
+ import os
7
+ import glob
8
+ import re
9
+ import requests
10
+ import time
11
+ import urllib.parse
12
+ from . import config
13
+ from .console import Console
14
+ from .check import Check
15
+ from .cleepapi import resolve_rpc_url
16
+ import subprocess
17
+
18
+ requests.packages.urllib3.disable_warnings()
19
+
20
+
21
+ class Ci:
22
+ """
23
+ Continuous Integration helpers
24
+ """
25
+
26
+ TESTS_REQUIREMENTS_TXT = "tests/requirements.txt"
27
+ EXTRACT_DIR = "/tmp/extract"
28
+ APP_FILENAME_PATTERN = "APP_FILENAME=['\"](.*)['\"]"
29
+
30
+ def __init__(self):
31
+ """
32
+ Constructor
33
+ """
34
+ self.logger = logging.getLogger(self.__class__.__name__)
35
+ self.command_url = None
36
+ self.health_url = None
37
+
38
+ def mod_check_package(self, package_path):
39
+ """
40
+ Check specified package content
41
+
42
+ Args:
43
+ package_path (string): package path
44
+
45
+ Returns:
46
+ dict: package informations::
47
+
48
+ {
49
+ module_name (str): module name
50
+ module_version (str): module version
51
+ has_tests_requirements (bool): True if tests/requirements.txt exists
52
+ }
53
+
54
+ Raises:
55
+ Exception if something bad detected
56
+ """
57
+ self.logger.info('Checking application package "%s"' % package_path)
58
+ _, module_name, module_version = os.path.basename(package_path).split("_")
59
+ module_version = module_version.replace(".zip", "")[1:]
60
+
61
+ # check raw file
62
+ if not module_version:
63
+ raise Exception("Invalid package filename")
64
+ if not re.match("\d+\.\d+\.\d+", module_version):
65
+ raise Exception("Invalid package filename")
66
+ console = Console()
67
+ resp = console.command('file --keep-going --mime-type "%s"' % package_path)
68
+ if resp["returncode"] != 0:
69
+ raise Exception("Unable to check file validity")
70
+ filetype = resp["stdout"][0].split(": ")[1].strip()
71
+ self.logger.debug("Filetype=%s" % filetype)
72
+ if filetype != "application/zip\\012- application/octet-stream":
73
+ raise Exception("Invalid application package file")
74
+
75
+ # check package structure
76
+ has_tests_requirements = False
77
+ checks = {
78
+ "dir_backend": False,
79
+ "dir_frontend": False,
80
+ "dir_tests": False,
81
+ "file_module_json": False,
82
+ "file_desc_json": False,
83
+ "file_module_py": False,
84
+ "file_backend_init_py": False,
85
+ "file_tests_init_py": False,
86
+ }
87
+ with zipfile.ZipFile(package_path, "r") as zp:
88
+ for zfile in zp.infolist():
89
+ if zfile.filename.startswith("backend/modules/%s" % module_name):
90
+ checks["dir_backend"] = True
91
+ if zfile.filename.startswith("frontend/js/modules/%s" % module_name):
92
+ checks["dir_frontend"] = True
93
+ if zfile.filename.startswith("tests"):
94
+ checks["dir_tests"] = True
95
+ if zfile.filename == "backend/modules/%s/%s.py" % (
96
+ module_name,
97
+ module_name,
98
+ ):
99
+ checks["file_module_py"] = True
100
+ if zfile.filename == "frontend/js/modules/%s/desc.json" % module_name:
101
+ checks["file_desc_json"] = True
102
+ if zfile.filename == "module.json":
103
+ checks["file_module_json"] = True
104
+ if zfile.filename == self.TESTS_REQUIREMENTS_TXT:
105
+ has_tests_requirements = True
106
+ if zfile.filename == "backend/modules/%s/__init__.py" % module_name:
107
+ checks["file_backend_init_py"] = True
108
+ if zfile.filename == "tests/__init__.py":
109
+ checks["file_tests_init_py"] = True
110
+
111
+ # special case for custom main module filename
112
+ if checks["file_backend_init_py"] and not checks["file_module_py"]:
113
+ content = (
114
+ zp.read("backend/modules/%s/__init__.py" % module_name)
115
+ .decode("utf8")
116
+ .strip()
117
+ )
118
+ matches = re.findall(self.APP_FILENAME_PATTERN, content)
119
+ if (
120
+ len(matches) == 1
121
+ and "backend/modules/%s/%s.py" % (module_name, matches[0])
122
+ in zp.namelist()
123
+ ):
124
+ checks["file_module_py"] = True
125
+
126
+ self.logger.debug("Checks results: %s" % checks)
127
+ if not all(checks.values()):
128
+ raise Exception(
129
+ "Invalid package structure. Make sure to build it with developer application"
130
+ )
131
+
132
+ return {
133
+ "module_name": module_name,
134
+ "module_version": module_version,
135
+ "has_test_requirements": has_tests_requirements,
136
+ }
137
+
138
+ def mod_install_sources(
139
+ self, package_path, package_infos, no_compatibility_check=False
140
+ ):
141
+ """
142
+ Install module package (zip archive) sources
143
+
144
+ Args:
145
+ package_path (string): package path
146
+ package_infos (dict): infos returned by mod_check_package function
147
+ no_compatitiblity_check (bool): do not check module compatibility (but only deps compat)
148
+
149
+ Raises:
150
+ Exception if error occured
151
+ """
152
+ module_name, module_version, has_tests_requirements = package_infos.values()
153
+ self.logger.info(
154
+ "Installing application %s v%s..." % (module_name, module_version)
155
+ )
156
+
157
+ cleep_proc = None
158
+ try:
159
+ # start cleep (non blocking)
160
+ self.logger.info(" Starting Cleep...")
161
+ cleep_proc = subprocess.Popen(
162
+ ["cleep", "--noro"], stdout=subprocess.PIPE, stderr=subprocess.PIPE
163
+ )
164
+ time.sleep(15)
165
+ self._bind_rpc()
166
+
167
+ # make sure to have latest modules.json version
168
+ self.logger.info(" Refreshing markets...")
169
+ resp = self._rpc_post(
170
+ {
171
+ "command": "check_modules_updates",
172
+ "to": "update",
173
+ "timeout": 60.0,
174
+ }
175
+ )
176
+ resp.raise_for_status()
177
+ resp_json = resp.json()
178
+ if resp_json["error"]:
179
+ raise Exception("Check_modules_updates command failed: %s" % resp_json)
180
+
181
+ # update "update" module to enjoy bug fixes
182
+ self.logger.info(' Updating "update" application...')
183
+ resp = self._rpc_post(
184
+ {
185
+ "command": "update_module",
186
+ "to": "update",
187
+ "params": {
188
+ "module_name": "update",
189
+ },
190
+ "timeout": 60.0,
191
+ }
192
+ )
193
+ resp.raise_for_status()
194
+ resp_json = resp.json()
195
+ self.logger.debug('Update "update" resp: %s' % resp_json)
196
+ if resp_json["error"]:
197
+ raise Exception('Updating app "update" failed: %s' % resp_json)
198
+ if resp_json["data"]:
199
+ self.__wait_for_cleep_process("update")
200
+ self.logger.info(" Restarting Cleep...")
201
+ cleep_proc.kill()
202
+ cleep_proc = subprocess.Popen(
203
+ ["cleep", "--noro"], stdout=subprocess.PIPE, stderr=subprocess.PIPE
204
+ )
205
+ time.sleep(15)
206
+ self._bind_rpc()
207
+
208
+ # install module in cleep (it will also install deps)
209
+ self.logger.info(' Installing "%s" application in Cleep' % module_name)
210
+ resp = self._rpc_post(
211
+ {
212
+ "command": "install_module",
213
+ "to": "update",
214
+ "params": {
215
+ "module_name": module_name,
216
+ "package": package_path,
217
+ "no_compatibility_check": no_compatibility_check,
218
+ },
219
+ "timeout": 60.0,
220
+ }
221
+ )
222
+ resp.raise_for_status()
223
+ resp_json = resp.json()
224
+ if resp_json["error"]:
225
+ raise Exception(
226
+ 'Installing "%s" app failed: %s' % (module_name, resp_json)
227
+ )
228
+ self.__wait_for_cleep_process(module_name)
229
+
230
+ # restart cleep
231
+ self.logger.info(" Restarting Cleep...")
232
+ cleep_proc.kill()
233
+ cleep_proc = subprocess.Popen(
234
+ ["cleep", "--noro"], stdout=subprocess.PIPE, stderr=subprocess.PIPE
235
+ )
236
+ time.sleep(15)
237
+ self._bind_rpc()
238
+
239
+ # check module is installed and running
240
+ self.logger.info(" Checking application is installed...")
241
+ resp = self._rpc_get(self.health_url)
242
+ self.logger.info("Health response: %s", resp.json())
243
+ resp.raise_for_status()
244
+ self.logger.info("Application and its dependencies successfully installed")
245
+
246
+ # install requirements.txt for tests
247
+ if has_tests_requirements:
248
+ self.logger.info("Installing tests python dependencies...")
249
+ console = Console()
250
+ requirements_txt_path = os.path.join(
251
+ config.MODULES_SRC, module_name, self.TESTS_REQUIREMENTS_TXT
252
+ )
253
+ self.logger.debug(
254
+ 'Tests requirements.txt path "%s"', requirements_txt_path
255
+ )
256
+ resp = console.command(
257
+ 'python3 -m pip install --trusted-host pypi.org -r "%s"'
258
+ % requirements_txt_path,
259
+ 900,
260
+ )
261
+ self.logger.debug("Resp: %s" % resp)
262
+ if resp["returncode"] != 0:
263
+ self.logger.error(
264
+ "Error installing tests requirements.txt: %s", resp
265
+ )
266
+ raise Exception(
267
+ "Error installing tests requirements.txt (killed=%s)"
268
+ % resp["killed"]
269
+ )
270
+
271
+ finally:
272
+ if cleep_proc:
273
+ cleep_proc.kill()
274
+
275
+ def _bind_rpc(self):
276
+ """
277
+ Detect Cleep RPC endpoint (HTTPS first, then HTTP)
278
+ """
279
+ base = resolve_rpc_url()
280
+ self.command_url = urllib.parse.urljoin(base + "/", "command")
281
+ self.health_url = urllib.parse.urljoin(base + "/", "health")
282
+
283
+ def _rpc_post(self, payload):
284
+ """
285
+ POST JSON to Cleep /command (self-signed TLS accepted).
286
+ """
287
+ return requests.post(self.command_url, json=payload, verify=False)
288
+
289
+ def _rpc_get(self, url):
290
+ """
291
+ GET Cleep RPC url (self-signed TLS accepted).
292
+ """
293
+ return requests.get(url, verify=False)
294
+
295
+ def __wait_for_cleep_process(self, module_name):
296
+ """
297
+ Wait for end of current Cleep process (install, update...)
298
+ """
299
+ while True:
300
+ time.sleep(1.0)
301
+ resp = self._rpc_post(
302
+ {
303
+ "command": "get_modules_updates",
304
+ "to": "update",
305
+ "timeout": 60.0,
306
+ }
307
+ )
308
+ resp.raise_for_status()
309
+ resp_json = resp.json()
310
+ if resp_json["error"]:
311
+ raise Exception("Get_modules_updates command failed")
312
+ module_updates = resp_json["data"].get(module_name)
313
+ self.logger.debug("Updates: %s" % module_updates)
314
+ if not module_updates:
315
+ raise Exception(
316
+ 'No "%s" application info in updates data' % module_name
317
+ )
318
+ if module_updates["processing"] == False:
319
+ if module_updates["update"]["failed"]:
320
+ raise Exception(
321
+ 'Application "%s" installation failed' % module_name
322
+ )
323
+ break
324
+
325
+ def mod_extract_sources(self, package_path, package_infos):
326
+ """
327
+ Extract module package (zip archive) in dev directory
328
+
329
+ Args:
330
+ package_path (string): package path
331
+ package_infos (dict): infos returned by mod_check_package function
332
+
333
+ Raises:
334
+ Exception if error occured
335
+ """
336
+ module_name, module_version, _ = package_infos.values()
337
+
338
+ # unzip content
339
+ self.logger.info(
340
+ 'Extracting archive "%s" to "%s"' % (package_path, self.EXTRACT_DIR)
341
+ )
342
+ with zipfile.ZipFile(package_path, "r") as package:
343
+ package.extractall(self.EXTRACT_DIR)
344
+
345
+ # install sources
346
+ os.makedirs(os.path.join(config.MODULES_SRC, module_name), exist_ok=True)
347
+ for filepath in glob.glob(self.EXTRACT_DIR + "/**/*.*", recursive=True):
348
+ if filepath.startswith(os.path.join(self.EXTRACT_DIR, "frontend")):
349
+ dest = filepath.replace(
350
+ os.path.join(
351
+ self.EXTRACT_DIR, "frontend/js/modules/%s" % module_name
352
+ ),
353
+ os.path.join(config.MODULES_SRC, module_name, "frontend"),
354
+ )
355
+ self.logger.debug(" -> frontend: %s" % dest)
356
+ elif filepath.startswith(os.path.join(self.EXTRACT_DIR, "backend")):
357
+ dest = filepath.replace(
358
+ os.path.join(self.EXTRACT_DIR, "backend/modules/%s" % module_name),
359
+ os.path.join(config.MODULES_SRC, module_name, "backend"),
360
+ )
361
+ self.logger.debug(" -> backend: %s" % dest)
362
+ elif filepath.startswith(os.path.join(self.EXTRACT_DIR, "tests")):
363
+ dest = filepath.replace(
364
+ os.path.join(self.EXTRACT_DIR, "tests"),
365
+ os.path.join(config.MODULES_SRC, module_name, "tests"),
366
+ )
367
+ self.logger.debug(" -> tests: %s" % dest)
368
+ elif filepath.startswith(os.path.join(self.EXTRACT_DIR, "scripts")):
369
+ dest = filepath.replace(
370
+ os.path.join(self.EXTRACT_DIR, "scripts"),
371
+ os.path.join(config.MODULES_SRC, module_name, "scripts"),
372
+ )
373
+ self.logger.debug(" -> scripts: %s" % dest)
374
+ else:
375
+ dest = filepath.replace(
376
+ self.EXTRACT_DIR, os.path.join(config.MODULES_SRC, module_name)
377
+ )
378
+ self.logger.debug(" -> other: %s" % dest)
379
+ os.makedirs(os.path.dirname(dest), exist_ok=True)
380
+ os.rename(filepath, dest)
381
+
382
+ def mod_check(self, module_name):
383
+ """
384
+ Perform some checkings (see check.py file) for continuous integration
385
+
386
+ Args:
387
+ module_name (string): module name
388
+
389
+ Raises:
390
+ Exception if error occured
391
+ """
392
+ check = Check()
393
+
394
+ check.check_backend(module_name)
395
+ check.check_frontend(module_name)
396
+ check.check_scripts(module_name)
397
+ check.check_tests(module_name)
@@ -10,6 +10,50 @@ import requests
10
10
  import json
11
11
  import urllib.parse
12
12
 
13
+ requests.packages.urllib3.disable_warnings()
14
+
15
+ # Cleep 0.1+ serves HTTPS by default; older versions listen on HTTP only.
16
+ DEFAULT_RPC_URLS = (
17
+ 'https://127.0.0.1:443',
18
+ 'http://127.0.0.1:80',
19
+ )
20
+
21
+
22
+ def resolve_rpc_url(urls=None, timeout=3.0):
23
+ """
24
+ Return the first reachable Cleep RPC base URL.
25
+
26
+ Tries HTTPS first (Cleep default), then HTTP. Self-signed certificates
27
+ are accepted. A candidate is considered reachable as soon as a TCP/TLS
28
+ session is established (any HTTP status).
29
+
30
+ Args:
31
+ urls (tuple): candidate base URLs
32
+ timeout (float): per-candidate timeout in seconds
33
+
34
+ Returns:
35
+ str: reachable base URL (no trailing slash)
36
+
37
+ Raises:
38
+ Exception: if none of the candidates respond
39
+ """
40
+ logger = logging.getLogger('CleepApi')
41
+ candidates = urls or DEFAULT_RPC_URLS
42
+ last_error = None
43
+ for base in candidates:
44
+ health_url = urllib.parse.urljoin(base.rstrip('/') + '/', 'health')
45
+ try:
46
+ logger.debug('Probing Cleep RPC %s', health_url)
47
+ requests.get(health_url, timeout=timeout, verify=False)
48
+ logger.info('Using Cleep RPC %s', base)
49
+ return base.rstrip('/')
50
+ except (requests.exceptions.ConnectionError, requests.exceptions.Timeout, requests.exceptions.SSLError) as error:
51
+ logger.debug('Cleep RPC not reachable at %s: %s', base, error)
52
+ last_error = error
53
+
54
+ raise Exception('Unable to reach Cleep RPC (tried %s): %s' % (', '.join(candidates), last_error))
55
+
56
+
13
57
  class CleepApi():
14
58
  """
15
59
  Cleep api helper
@@ -19,7 +63,10 @@ class CleepApi():
19
63
  self.logger = logging.getLogger(self.__class__.__name__)
20
64
 
21
65
  if not rpc_url:
22
- rpc_url = 'http://0.0.0.0:80'
66
+ try:
67
+ rpc_url = resolve_rpc_url()
68
+ except Exception:
69
+ rpc_url = DEFAULT_RPC_URLS[0]
23
70
  self.logger.debug('RPC url: %s', rpc_url)
24
71
 
25
72
  self.command_url = urllib.parse.urljoin(rpc_url, "/command")