cmdbox 0.5.1.2__py3-none-any.whl → 0.5.3__py3-none-any.whl
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.
Potentially problematic release.
This version of cmdbox might be problematic. Click here for more details.
- cmdbox/app/app.py +4 -2
- cmdbox/app/auth/signin.py +634 -631
- cmdbox/app/client.py +10 -10
- cmdbox/app/common.py +50 -6
- cmdbox/app/commons/convert.py +9 -0
- cmdbox/app/commons/module.py +113 -113
- cmdbox/app/commons/redis_client.py +40 -29
- cmdbox/app/edge.py +4 -4
- cmdbox/app/features/cli/audit_base.py +138 -0
- cmdbox/app/features/cli/cmdbox_audit_createdb.py +224 -0
- cmdbox/app/features/cli/cmdbox_audit_delete.py +308 -0
- cmdbox/app/features/cli/cmdbox_audit_search.py +416 -0
- cmdbox/app/features/cli/cmdbox_audit_write.py +247 -0
- cmdbox/app/features/cli/cmdbox_client_file_copy.py +207 -207
- cmdbox/app/features/cli/cmdbox_client_file_download.py +207 -207
- cmdbox/app/features/cli/cmdbox_client_file_list.py +193 -193
- cmdbox/app/features/cli/cmdbox_client_file_mkdir.py +191 -191
- cmdbox/app/features/cli/cmdbox_client_file_move.py +199 -199
- cmdbox/app/features/cli/cmdbox_client_file_remove.py +190 -190
- cmdbox/app/features/cli/cmdbox_client_file_rmdir.py +190 -190
- cmdbox/app/features/cli/cmdbox_client_file_upload.py +212 -212
- cmdbox/app/features/cli/cmdbox_client_server_info.py +166 -166
- cmdbox/app/features/cli/cmdbox_server_list.py +88 -88
- cmdbox/app/features/cli/cmdbox_server_stop.py +138 -138
- cmdbox/app/features/web/cmdbox_web_audit.py +81 -0
- cmdbox/app/features/web/cmdbox_web_audit_metrics.py +72 -0
- cmdbox/app/features/web/cmdbox_web_del_cmd.py +2 -0
- cmdbox/app/features/web/cmdbox_web_del_pipe.py +1 -0
- cmdbox/app/features/web/cmdbox_web_do_signin.py +12 -2
- cmdbox/app/features/web/cmdbox_web_do_signout.py +1 -0
- cmdbox/app/features/web/cmdbox_web_exec_cmd.py +31 -2
- cmdbox/app/features/web/cmdbox_web_exec_pipe.py +1 -0
- cmdbox/app/features/web/cmdbox_web_filer download.py +43 -42
- cmdbox/app/features/web/cmdbox_web_filer.py +1 -0
- cmdbox/app/features/web/cmdbox_web_filer_upload.py +65 -64
- cmdbox/app/features/web/cmdbox_web_gui.py +166 -165
- cmdbox/app/features/web/cmdbox_web_load_pin.py +43 -43
- cmdbox/app/features/web/cmdbox_web_raw_pipe.py +87 -87
- cmdbox/app/features/web/cmdbox_web_save_cmd.py +1 -0
- cmdbox/app/features/web/cmdbox_web_save_pin.py +42 -42
- cmdbox/app/features/web/cmdbox_web_save_pipe.py +1 -0
- cmdbox/app/features/web/cmdbox_web_user_data.py +58 -0
- cmdbox/app/features/web/cmdbox_web_users.py +12 -0
- cmdbox/app/options.py +788 -601
- cmdbox/app/web.py +7 -1
- cmdbox/extensions/features.yml +23 -0
- cmdbox/extensions/sample_project/sample/app/features/cli/sample_client_time.py +82 -82
- cmdbox/extensions/sample_project/sample/app/features/cli/sample_server_time.py +145 -145
- cmdbox/extensions/user_list.yml +5 -0
- cmdbox/licenses/{LICENSE.Sphinx.8.1.3(BSD License).txt → LICENSE.Sphinx.8.2.3(UNKNOWN).txt} +1 -1
- cmdbox/licenses/LICENSE.argcomplete.3.6.2(Apache Software License).txt +177 -0
- cmdbox/licenses/{LICENSE.babel.2.16.0(BSD License).txt → LICENSE.babel.2.17.0(BSD License).txt } +1 -1
- cmdbox/licenses/{LICENSE.pkginfo.1.10.0(MIT License).txt → LICENSE.charset-normalizer.3.4.1(MIT License).txt } +1 -1
- cmdbox/licenses/LICENSE.gevent.25.4.1(MIT).txt +25 -0
- cmdbox/licenses/LICENSE.greenlet.3.2.0(MIT AND Python-2.0).txt +30 -0
- cmdbox/licenses/LICENSE.gunicorn.23.0.0(MIT License).txt +23 -0
- cmdbox/licenses/LICENSE.importlib_metadata.8.6.1(Apache Software License).txt +202 -0
- cmdbox/licenses/LICENSE.nh3.0.2.21(MIT).txt +21 -0
- cmdbox/licenses/{LICENSE.pillow.11.0.0(CMU License (MIT-CMU)).txt → LICENSE.pillow.11.1.0(CMU License (MIT-CMU)).txt } +27 -40
- cmdbox/licenses/LICENSE.pillow.11.2.1(UNKNOWN).txt +1200 -0
- cmdbox/licenses/LICENSE.plyer.2.1.0(MIT License).txt +19 -0
- cmdbox/licenses/LICENSE.prompt_toolkit.3.0.50(BSD License).txt +27 -0
- cmdbox/licenses/LICENSE.prompt_toolkit.3.0.51(BSD License).txt +27 -0
- cmdbox/licenses/LICENSE.psycopg-binary.3.2.6(GNU Lesser General Public License v3 (LGPLv3)).txt +165 -0
- cmdbox/licenses/LICENSE.psycopg-pool.3.2.6(GNU Lesser General Public License v3 (LGPLv3)).txt +165 -0
- cmdbox/licenses/LICENSE.psycopg.3.2.6(GNU Lesser General Public License v3 (LGPLv3)).txt +165 -0
- cmdbox/licenses/LICENSE.pycryptodome.3.22.0(BSD License; Public Domain).txt +61 -0
- cmdbox/licenses/LICENSE.pydantic.2.11.3(MIT License).txt +21 -0
- cmdbox/licenses/LICENSE.pydantic_core.2.33.1(MIT License).txt +21 -0
- cmdbox/licenses/LICENSE.pystray.0.19.5(GNU Lesser General Public License v3 (LGPLv3)).txt +674 -0
- cmdbox/licenses/LICENSE.questionary.2.1.0(MIT License).txt +19 -0
- cmdbox/licenses/LICENSE.roman-numerals-py.3.1.0(CC0 1.0 Universal (CC0 1.0) Public Domain Dedication; Zero-Clause BSD (0BSD)).txt +146 -0
- cmdbox/licenses/{LICENSE.six.1.16.0(MIT License).txt → LICENSE.six.1.17.0(MIT License).txt } +1 -1
- cmdbox/licenses/LICENSE.starlette.0.46.2(BSD License).txt +27 -0
- cmdbox/licenses/{LICENSE.charset-normalizer.3.4.0(MIT License).txt → LICENSE.typing-inspection.0.4.0(MIT License).txt } +2 -2
- cmdbox/licenses/LICENSE.typing_extensions.4.13.2(UNKNOWN).txt +279 -0
- cmdbox/licenses/LICENSE.tzdata.2025.2(Apache Software License).txt +15 -0
- cmdbox/licenses/LICENSE.urllib3.2.4.0(UNKNOWN).txt +21 -0
- cmdbox/licenses/LICENSE.uvicorn.0.34.1(BSD License).txt +27 -0
- cmdbox/licenses/LICENSE.watchfiles.1.0.5(MIT License).txt +21 -0
- cmdbox/licenses/files.txt +49 -38
- cmdbox/logconf_audit.yml +30 -0
- cmdbox/logconf_cmdbox.yml +30 -0
- cmdbox/version.py +2 -2
- cmdbox/web/assets/apexcharts/apexcharts.css +679 -0
- cmdbox/web/assets/apexcharts/apexcharts.min.js +38 -0
- cmdbox/web/assets/cmdbox/audit.js +340 -0
- cmdbox/web/assets/cmdbox/color_mode.css +520 -0
- cmdbox/web/assets/cmdbox/common.js +416 -24
- cmdbox/web/assets/cmdbox/filer_modal.js +1 -1
- cmdbox/web/assets/cmdbox/list_cmd.js +10 -275
- cmdbox/web/assets/cmdbox/list_pipe.js +3 -3
- cmdbox/web/assets/cmdbox/main.js +2 -2
- cmdbox/web/assets/cmdbox/result.js +2 -2
- cmdbox/web/assets/cmdbox/signin.js +2 -2
- cmdbox/web/assets/cmdbox/users.js +19 -20
- cmdbox/web/assets/cmdbox/view_raw.js +1 -1
- cmdbox/web/assets/cmdbox/view_result.js +11 -13
- cmdbox/web/assets/filer/filer.js +2 -2
- cmdbox/web/assets/filer/main.js +2 -2
- cmdbox/web/assets_license_list.txt +4 -1
- cmdbox/web/audit.html +268 -0
- cmdbox/web/filer.html +37 -12
- cmdbox/web/gui.html +36 -53
- cmdbox/web/result.html +24 -3
- cmdbox/web/signin.html +35 -14
- cmdbox/web/users.html +21 -3
- {cmdbox-0.5.1.2.dist-info → cmdbox-0.5.3.dist-info}/METADATA +28 -5
- {cmdbox-0.5.1.2.dist-info → cmdbox-0.5.3.dist-info}/RECORD +142 -103
- {cmdbox-0.5.1.2.dist-info → cmdbox-0.5.3.dist-info}/entry_points.txt +0 -1
- cmdbox/licenses/LICENSE.nh3.0.2.18(MIT).txt +0 -1
- /cmdbox/licenses/{LICENSE.Jinja2.3.1.4(BSD License).txt → LICENSE.Jinja2.3.1.6(BSD License).txt} +0 -0
- /cmdbox/licenses/{LICENSE.Pygments.2.18.0(BSD License).txt → LICENSE.Pygments.2.19.1(BSD License).txt} +0 -0
- /cmdbox/licenses/{LICENSE.anyio.4.6.2.post1(MIT License).txt → LICENSE.anyio.4.9.0(MIT License).txt} +0 -0
- /cmdbox/licenses/{LICENSE.argcomplete.3.5.1(Apache Software License).txt → LICENSE.argcomplete.3.6.1(Apache Software License).txt} +0 -0
- /cmdbox/licenses/{LICENSE.certifi.2024.8.30(Mozilla Public License 2.0 (MPL 2.0)).txt → LICENSE.certifi.2025.1.31(Mozilla Public License 2.0 (MPL 2.0)).txt} +0 -0
- /cmdbox/licenses/{LICENSE.click.8.1.7(BSD License).txt → LICENSE.click.8.1.8(BSD License).txt} +0 -0
- /cmdbox/licenses/{LICENSE.cryptography.43.0.3(Apache Software License; BSD License).txt → LICENSE.cryptography.44.0.2(Apache Software License; BSD License).txt} +0 -0
- /cmdbox/licenses/{LICENSE.fastapi.0.115.5(MIT License).txt → LICENSE.fastapi.0.115.12(MIT License).txt} +0 -0
- /cmdbox/licenses/{LICENSE.importlib_metadata.8.5.0(Apache Software License).txt → LICENSE.id.1.5.0(Apache Software License).txt} +0 -0
- /cmdbox/licenses/{LICENSE.keyring.25.5.0(MIT License).txt → LICENSE.keyring.25.6.0(MIT License).txt} +0 -0
- /cmdbox/licenses/{LICENSE.more-itertools.10.5.0(MIT License).txt → LICENSE.more-itertools.10.6.0(MIT License).txt} +0 -0
- /cmdbox/licenses/{LICENSE.numpy.2.1.3(BSD License).txt → LICENSE.numpy.2.2.4(BSD License).txt} +0 -0
- /cmdbox/licenses/{LICENSE.prettytable.3.12.0(BSD License).txt → LICENSE.prettytable.3.16.0(UNKNOWN).txt} +0 -0
- /cmdbox/licenses/{LICENSE.pydantic.2.10.2(MIT License).txt → LICENSE.pydantic.2.11.1(MIT License).txt} +0 -0
- /cmdbox/licenses/{LICENSE.pydantic_core.2.27.1(MIT License).txt → LICENSE.pydantic_core.2.33.0(MIT License).txt} +0 -0
- /cmdbox/licenses/{LICENSE.python-dotenv.1.0.1(BSD License).txt → LICENSE.python-dotenv.1.1.0(BSD License).txt} +0 -0
- /cmdbox/licenses/{LICENSE.python-multipart.0.0.17(Apache Software License).txt → LICENSE.python-multipart.0.0.20(Apache Software License).txt} +0 -0
- /cmdbox/licenses/{LICENSE.redis.5.2.0(MIT License).txt → LICENSE.redis.5.2.1(MIT License).txt} +0 -0
- /cmdbox/licenses/{LICENSE.rich.13.9.4(MIT License).txt → LICENSE.rich.14.0.0(MIT License).txt} +0 -0
- /cmdbox/licenses/{LICENSE.sphinx-intl.2.3.0(BSD License).txt → LICENSE.sphinx-intl.2.3.1(BSD License).txt} +0 -0
- /cmdbox/licenses/{LICENSE.starlette.0.41.3(BSD License).txt → LICENSE.starlette.0.46.1(BSD License).txt} +0 -0
- /cmdbox/licenses/{LICENSE.tomli.2.1.0(MIT License).txt → LICENSE.tomli.2.2.1(MIT License).txt} +0 -0
- /cmdbox/licenses/{LICENSE.twine.5.1.1(Apache Software License).txt → LICENSE.twine.6.1.0(Apache Software License).txt} +0 -0
- /cmdbox/licenses/{LICENSE.typing_extensions.4.12.2(Python Software Foundation License).txt → LICENSE.typing_extensions.4.13.0(UNKNOWN).txt} +0 -0
- /cmdbox/licenses/{LICENSE.urllib3.2.2.3(MIT License).txt → LICENSE.urllib3.2.3.0(MIT License).txt} +0 -0
- /cmdbox/licenses/{LICENSE.uvicorn.0.32.1(BSD License).txt → LICENSE.uvicorn.0.34.0(BSD License).txt} +0 -0
- /cmdbox/licenses/{LICENSE.watchfiles.1.0.0(MIT License).txt → LICENSE.watchfiles.1.0.4(MIT License).txt} +0 -0
- /cmdbox/licenses/{LICENSE.websockets.14.1(BSD License).txt → LICENSE.websockets.15.0.1(BSD License).txt} +0 -0
- /cmdbox/licenses/{LICENSE.zope.interface.7.1.1(Zope Public License).txt → LICENSE.zope.interface.7.2(Zope Public License).txt} +0 -0
- {cmdbox-0.5.1.2.dist-info → cmdbox-0.5.3.dist-info}/LICENSE +0 -0
- {cmdbox-0.5.1.2.dist-info → cmdbox-0.5.3.dist-info}/WHEEL +0 -0
- {cmdbox-0.5.1.2.dist-info → cmdbox-0.5.3.dist-info}/top_level.txt +0 -0
cmdbox/app/options.py
CHANGED
|
@@ -1,601 +1,788 @@
|
|
|
1
|
-
from cmdbox.app import common, feature
|
|
2
|
-
from cmdbox.app.commons import module
|
|
3
|
-
from fastapi
|
|
4
|
-
from
|
|
5
|
-
from
|
|
6
|
-
from
|
|
7
|
-
import
|
|
8
|
-
import
|
|
9
|
-
import
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
def
|
|
31
|
-
if
|
|
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
|
-
mode
|
|
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
|
-
opt['
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
opt['
|
|
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
|
-
self._options["
|
|
273
|
-
short="
|
|
274
|
-
discription_ja="
|
|
275
|
-
discription_en="
|
|
276
|
-
choice=
|
|
277
|
-
self._options["
|
|
278
|
-
short="
|
|
279
|
-
discription_ja="
|
|
280
|
-
discription_en="Specify the
|
|
281
|
-
choice=
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
if
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
if
|
|
530
|
-
del
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
1
|
+
from cmdbox.app import common, feature, web
|
|
2
|
+
from cmdbox.app.commons import module
|
|
3
|
+
from fastapi import Request
|
|
4
|
+
from fastapi.routing import APIRoute
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from starlette.routing import Route
|
|
8
|
+
from typing import List, Dict, Any
|
|
9
|
+
import argparse
|
|
10
|
+
import functools
|
|
11
|
+
import locale
|
|
12
|
+
import logging
|
|
13
|
+
import re
|
|
14
|
+
import time
|
|
15
|
+
import uuid
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class Options:
|
|
19
|
+
T_INT = 'int'
|
|
20
|
+
T_FLOAT = 'float'
|
|
21
|
+
T_BOOL = 'bool'
|
|
22
|
+
T_STR = 'str'
|
|
23
|
+
T_DATE = 'date'
|
|
24
|
+
T_DATETIME = 'datetime'
|
|
25
|
+
T_DICT = 'dict'
|
|
26
|
+
T_TEXT = 'text'
|
|
27
|
+
T_FILE = 'file'
|
|
28
|
+
T_DIR = 'dir'
|
|
29
|
+
|
|
30
|
+
def __setattr__(self, name:str, value):
|
|
31
|
+
if name.startswith("T_") and name in self.__dict__:
|
|
32
|
+
raise ValueError(f'Cannot set attribute. ({name})')
|
|
33
|
+
self.__dict__[name] = value
|
|
34
|
+
|
|
35
|
+
_instance = None
|
|
36
|
+
|
|
37
|
+
@staticmethod
|
|
38
|
+
def getInstance(appcls=None, ver=None):
|
|
39
|
+
if Options._instance is None:
|
|
40
|
+
Options._instance = Options(appcls=appcls, ver=ver)
|
|
41
|
+
return Options._instance
|
|
42
|
+
|
|
43
|
+
def __init__(self, appcls=None, ver=None):
|
|
44
|
+
self.appcls = appcls
|
|
45
|
+
self.ver = ver
|
|
46
|
+
self.default_logger = common.default_logger(False, ver=self.ver, webcall=True)
|
|
47
|
+
self.features_yml_data = None
|
|
48
|
+
self.features_loaded = dict()
|
|
49
|
+
self.aliases_loaded_cli = False
|
|
50
|
+
self.aliases_loaded_web = False
|
|
51
|
+
self.audit_loaded = False
|
|
52
|
+
self.init_options()
|
|
53
|
+
|
|
54
|
+
def get_mode_keys(self) -> List[str]:
|
|
55
|
+
return [key for key,val in self._options["mode"].items() if type(val) == dict]
|
|
56
|
+
|
|
57
|
+
def get_modes(self) -> List[Dict[str, str]]:
|
|
58
|
+
"""
|
|
59
|
+
起動モードの選択肢を取得します。
|
|
60
|
+
Returns:
|
|
61
|
+
List[Dict[str, str]]: 起動モードの選択肢
|
|
62
|
+
"""
|
|
63
|
+
return [''] + [{key:val} for key,val in self._options["mode"].items() if type(val) == dict]
|
|
64
|
+
|
|
65
|
+
def get_cmd_keys(self, mode:str) -> List[str]:
|
|
66
|
+
if mode not in self._options["cmd"]:
|
|
67
|
+
return []
|
|
68
|
+
return [key for key,val in self._options["cmd"][mode].items() if type(val) == dict]
|
|
69
|
+
|
|
70
|
+
def get_cmds(self, mode:str) -> List[Dict[str, str]]:
|
|
71
|
+
"""
|
|
72
|
+
コマンドの選択肢を取得します。
|
|
73
|
+
Args:
|
|
74
|
+
mode: 起動モード
|
|
75
|
+
Returns:
|
|
76
|
+
List[Dict[str, str]]: コマンドの選択肢
|
|
77
|
+
"""
|
|
78
|
+
if mode not in self._options["cmd"]:
|
|
79
|
+
return ['Please select mode.']
|
|
80
|
+
ret = [{key:val} for key,val in self._options["cmd"][mode].items() if type(val) == dict]
|
|
81
|
+
if len(ret) > 0:
|
|
82
|
+
return [''] + ret
|
|
83
|
+
return ['Please select mode.']
|
|
84
|
+
|
|
85
|
+
def get_cmd_attr(self, mode:str, cmd:str, attr:str) -> Any:
|
|
86
|
+
"""
|
|
87
|
+
コマンドの属性を取得します。
|
|
88
|
+
Args:
|
|
89
|
+
mode: 起動モード
|
|
90
|
+
cmd: コマンド
|
|
91
|
+
attr: 属性
|
|
92
|
+
Returns:
|
|
93
|
+
Any: 属性の値
|
|
94
|
+
"""
|
|
95
|
+
if mode not in self._options["cmd"]:
|
|
96
|
+
return [f'Unknown mode. ({mode})']
|
|
97
|
+
if cmd is None or cmd == "" or cmd not in self._options["cmd"][mode]:
|
|
98
|
+
return []
|
|
99
|
+
if attr not in self._options["cmd"][mode][cmd]:
|
|
100
|
+
return None
|
|
101
|
+
return self._options["cmd"][mode][cmd][attr]
|
|
102
|
+
|
|
103
|
+
def get_svcmd_feature(self, svcmd:str) -> Any:
|
|
104
|
+
"""
|
|
105
|
+
サーバー側のコマンドのフューチャーを取得します。
|
|
106
|
+
|
|
107
|
+
Args:
|
|
108
|
+
svcmd: サーバー側のコマンド
|
|
109
|
+
Returns:
|
|
110
|
+
feature.Feature: フューチャー
|
|
111
|
+
"""
|
|
112
|
+
if svcmd is None or svcmd == "":
|
|
113
|
+
return None
|
|
114
|
+
if svcmd not in self._options["svcmd"]:
|
|
115
|
+
return None
|
|
116
|
+
return self._options["svcmd"][svcmd]
|
|
117
|
+
|
|
118
|
+
def get_cmd_choices(self, mode:str, cmd:str, webmode:bool=False) -> List[Dict[str, Any]]:
|
|
119
|
+
"""
|
|
120
|
+
コマンドのオプション一覧を取得します。
|
|
121
|
+
Args:
|
|
122
|
+
mode: 起動モード
|
|
123
|
+
cmd: コマンド
|
|
124
|
+
webmode (bool, optional): Webモードからの呼び出し. Defaults to False
|
|
125
|
+
Returns:
|
|
126
|
+
List[Dict[str, Any]]: オプションの選択肢
|
|
127
|
+
"""
|
|
128
|
+
opts = self.get_cmd_attr(mode, cmd, "choice")
|
|
129
|
+
ret = []
|
|
130
|
+
for o in opts:
|
|
131
|
+
if not webmode or type(o) is not dict:
|
|
132
|
+
ret.append(o)
|
|
133
|
+
continue
|
|
134
|
+
o = o.copy()
|
|
135
|
+
if 'web' in o and o['web'] == 'mask':
|
|
136
|
+
o['default'] = '********'
|
|
137
|
+
ret.append(o)
|
|
138
|
+
return ret
|
|
139
|
+
|
|
140
|
+
def get_cmd_opt(self, mode:str, cmd:str, opt:str, webmode:bool=False) -> Dict[str, Any]:
|
|
141
|
+
"""
|
|
142
|
+
コマンドのオプションを取得します。
|
|
143
|
+
Args:
|
|
144
|
+
mode: 起動モード
|
|
145
|
+
cmd: コマンド
|
|
146
|
+
opt: オプション
|
|
147
|
+
webmode (bool, optional): Webモードからの呼び出し. Defaults to False
|
|
148
|
+
Returns:
|
|
149
|
+
Dict[str, Any]: オプションの値
|
|
150
|
+
"""
|
|
151
|
+
opts = self.get_cmd_choices(mode, cmd, webmode)
|
|
152
|
+
for o in opts:
|
|
153
|
+
if 'opt' in o and o['opt'] == opt:
|
|
154
|
+
return o
|
|
155
|
+
return None
|
|
156
|
+
|
|
157
|
+
def list_options(self):
|
|
158
|
+
def _list(ret, key, val):
|
|
159
|
+
if type(val) != dict or 'type' not in val:
|
|
160
|
+
return
|
|
161
|
+
opt = dict()
|
|
162
|
+
if val['type'] == Options.T_INT:
|
|
163
|
+
opt['type'] = int
|
|
164
|
+
opt['action'] = 'append' if val['multi'] else None
|
|
165
|
+
elif val['type'] == Options.T_FLOAT:
|
|
166
|
+
opt['type'] = float
|
|
167
|
+
opt['action'] = 'append' if val['multi'] else None
|
|
168
|
+
elif val['type'] == Options.T_BOOL:
|
|
169
|
+
opt['type'] = bool
|
|
170
|
+
opt['action'] = 'store_true'
|
|
171
|
+
elif val['type'] == Options.T_DICT:
|
|
172
|
+
opt['type'] = dict
|
|
173
|
+
if not val['multi']:
|
|
174
|
+
raise ValueError(f'list_options: The multi must be True if type is dict. key={key}, val={val}')
|
|
175
|
+
opt['action'] = 'append'
|
|
176
|
+
else:
|
|
177
|
+
opt['type'] = str
|
|
178
|
+
opt['action'] = 'append' if val['multi'] else None
|
|
179
|
+
o = [f'-{val["short"]}'] if "short" in val else []
|
|
180
|
+
o += [f'--{key}']
|
|
181
|
+
language, _ = locale.getlocale()
|
|
182
|
+
opt['help'] = val['discription_en'] if language.find('Japan') < 0 and language.find('ja_JP') < 0 else val['discription_ja']
|
|
183
|
+
opt['default'] = val['default']
|
|
184
|
+
if val['multi'] and val['default'] is not None:
|
|
185
|
+
raise ValueError(f'list_options: The default value must be None if multi is True. key={key}, val={val}')
|
|
186
|
+
opt['opts'] = o
|
|
187
|
+
if val['choice'] is not None:
|
|
188
|
+
opt['choices'] = []
|
|
189
|
+
for c in val['choice']:
|
|
190
|
+
if type(c) == dict:
|
|
191
|
+
opt['choices'] += [c['opt']]
|
|
192
|
+
elif c is not None and c != "":
|
|
193
|
+
opt['choices'] += [c]
|
|
194
|
+
else:
|
|
195
|
+
opt['choices'] = None
|
|
196
|
+
ret[key] = opt
|
|
197
|
+
ret = dict()
|
|
198
|
+
for k, v in self._options.items():
|
|
199
|
+
_list(ret, k, v)
|
|
200
|
+
#for mode in self._options["mode"]['choice']:
|
|
201
|
+
for _, cmd in self._options["cmd"].items():
|
|
202
|
+
if type(cmd) is not dict:
|
|
203
|
+
continue
|
|
204
|
+
for _, opt in cmd.items():
|
|
205
|
+
if type(opt) is not dict:
|
|
206
|
+
continue
|
|
207
|
+
for o in opt["choice"]:
|
|
208
|
+
if type(o) is not dict:
|
|
209
|
+
continue
|
|
210
|
+
_list(ret, o['opt'], o)
|
|
211
|
+
return ret
|
|
212
|
+
|
|
213
|
+
def mk_opt_list(self, opt:dict, webmode:bool=False) -> List[str]:
|
|
214
|
+
opt_schema = self.get_cmd_choices(opt['mode'], opt['cmd'], webmode)
|
|
215
|
+
opt_list = ['-m', opt['mode'], '-c', opt['cmd']]
|
|
216
|
+
file_dict = dict()
|
|
217
|
+
for key, val in opt.items():
|
|
218
|
+
if key in ['stdout_log', 'capture_stdout']:
|
|
219
|
+
continue
|
|
220
|
+
schema = [schema for schema in opt_schema if type(schema) is dict and schema['opt'] == key]
|
|
221
|
+
if len(schema) == 0 or val == '':
|
|
222
|
+
continue
|
|
223
|
+
if schema[0]['type'] == Options.T_BOOL:
|
|
224
|
+
if val:
|
|
225
|
+
opt_list.append(f"--{key}")
|
|
226
|
+
continue
|
|
227
|
+
if type(val) == list:
|
|
228
|
+
for v in val:
|
|
229
|
+
if v is None or v == '':
|
|
230
|
+
continue
|
|
231
|
+
opt_list.append(f"--{key}")
|
|
232
|
+
if str(v).find(' ') >= 0:
|
|
233
|
+
opt_list.append(f'"{v}"')
|
|
234
|
+
else:
|
|
235
|
+
opt_list.append(str(v))
|
|
236
|
+
elif type(val) == dict:
|
|
237
|
+
for k,v in val.items():
|
|
238
|
+
if k is None or k == '' or v is None or v == '':
|
|
239
|
+
continue
|
|
240
|
+
opt_list.append(f"--{key}")
|
|
241
|
+
k = f'"{k}"' if str(k).find(' ') >= 0 else str(k)
|
|
242
|
+
v = f'"{v}"' if str(v).find(' ') >= 0 else str(v)
|
|
243
|
+
opt_list.append(f'{k}={v}')
|
|
244
|
+
elif val is not None and val != '':
|
|
245
|
+
opt_list.append(f"--{key}")
|
|
246
|
+
if str(val).find(' ') >= 0:
|
|
247
|
+
opt_list.append(f'"{val}"')
|
|
248
|
+
else:
|
|
249
|
+
opt_list.append(str(val))
|
|
250
|
+
if 'fileio' in schema[0] and schema[0]['fileio'] == 'in' and type(val) != str:
|
|
251
|
+
file_dict[key] = val
|
|
252
|
+
return opt_list, file_dict
|
|
253
|
+
|
|
254
|
+
def init_options(self):
|
|
255
|
+
self._options = dict()
|
|
256
|
+
self._options["version"] = dict(
|
|
257
|
+
short="v", type=Options.T_BOOL, default=None, required=False, multi=False, hide=True, choice=None,
|
|
258
|
+
discription_ja="バージョン表示",
|
|
259
|
+
discription_en="Display version")
|
|
260
|
+
self._options["useopt"] = dict(
|
|
261
|
+
short="u", type=Options.T_STR, default=None, required=False, multi=False, hide=True, choice=None,
|
|
262
|
+
discription_ja="オプションを保存しているファイルを使用します。",
|
|
263
|
+
discription_en="Use the file that saves the options.")
|
|
264
|
+
self._options["saveopt"] = dict(
|
|
265
|
+
short="s", type=Options.T_BOOL, default=None, required=False, multi=False, hide=True, choice=[True, False],
|
|
266
|
+
discription_ja="指定しているオプションを `-u` で指定したファイルに保存します。",
|
|
267
|
+
discription_en="Save the specified options to the file specified by `-u`.")
|
|
268
|
+
self._options["debug"] = dict(
|
|
269
|
+
short="d", type=Options.T_BOOL, default=False, required=False, multi=False, hide=True, choice=[True, False],
|
|
270
|
+
discription_ja="デバックモードで起動します。",
|
|
271
|
+
discription_en="Starts in debug mode.")
|
|
272
|
+
self._options["format"] = dict(
|
|
273
|
+
short="f", type=Options.T_BOOL, default=None, required=False, multi=False, hide=True,
|
|
274
|
+
discription_ja="処理結果を見やすい形式で出力します。指定しない場合json形式で出力します。",
|
|
275
|
+
discription_en="Output the processing result in an easy-to-read format. If not specified, output in json format.",
|
|
276
|
+
choice=None)
|
|
277
|
+
self._options["mode"] = dict(
|
|
278
|
+
short="m", type=Options.T_STR, default=None, required=True, multi=False, hide=True,
|
|
279
|
+
discription_ja="起動モードを指定します。",
|
|
280
|
+
discription_en="Specify the startup mode.",
|
|
281
|
+
choice=[])
|
|
282
|
+
self._options["cmd"] = dict(
|
|
283
|
+
short="c", type=Options.T_STR, default=None, required=True, multi=False, hide=True,
|
|
284
|
+
discription_ja="コマンドを指定します。",
|
|
285
|
+
discription_en="Specify the command.",
|
|
286
|
+
choice=[])
|
|
287
|
+
self._options["tag"] = dict(
|
|
288
|
+
short="t", type=Options.T_STR, default=None, required=False, multi=True, hide=True,
|
|
289
|
+
discription_ja="このコマンドのタグを指定します。",
|
|
290
|
+
discription_en="Specify the tag for this command.",
|
|
291
|
+
choice=None)
|
|
292
|
+
self._options["clmsg_id"] = dict(
|
|
293
|
+
type=Options.T_STR, default=None, required=False, multi=False, hide=True,
|
|
294
|
+
discription_ja="クライアントのメッセージIDを指定します。省略した場合はuuid4で生成されます。",
|
|
295
|
+
discription_en="Specifies the message ID of the client. If omitted, uuid4 will be generated.",
|
|
296
|
+
choice=None)
|
|
297
|
+
|
|
298
|
+
def init_debugoption(self):
|
|
299
|
+
# デバックオプションを追加
|
|
300
|
+
self._options["debug"]["opt"] = "debug"
|
|
301
|
+
self._options["tag"]["opt"] = "tag"
|
|
302
|
+
self._options["clmsg_id"]["opt"] = "clmsg_id"
|
|
303
|
+
for key, mode in self._options["cmd"].items():
|
|
304
|
+
if type(mode) is not dict:
|
|
305
|
+
continue
|
|
306
|
+
mode['opt'] = key
|
|
307
|
+
for k, c in mode.items():
|
|
308
|
+
if type(c) is not dict:
|
|
309
|
+
continue
|
|
310
|
+
c["opt"] = k
|
|
311
|
+
if "debug" not in [_o['opt'] for _o in c["choice"]]:
|
|
312
|
+
c["choice"].append(self._options["debug"])
|
|
313
|
+
if "tag" not in [_o['opt'] for _o in c["choice"]]:
|
|
314
|
+
c["choice"].append(self._options["tag"])
|
|
315
|
+
if "clmsg_id" not in [_o['opt'] for _o in c["choice"]]:
|
|
316
|
+
c["choice"].append(self._options["clmsg_id"])
|
|
317
|
+
if c["opt"] not in [_o['opt'] for _o in self._options["cmd"]["choice"]]:
|
|
318
|
+
self._options["cmd"]["choice"] += [c]
|
|
319
|
+
self._options["mode"][key] = mode
|
|
320
|
+
self._options["mode"]["choice"] += [mode]
|
|
321
|
+
|
|
322
|
+
def load_svcmd(self, package_name:str, prefix:str="cmdbox_", excludes:list=[], appcls=None, ver=None, logger:logging.Logger=None, isloaded:bool=True):
|
|
323
|
+
"""
|
|
324
|
+
指定されたパッケージの指定された接頭語を持つモジュールを読み込みます。
|
|
325
|
+
|
|
326
|
+
Args:
|
|
327
|
+
package_name (str): パッケージ名
|
|
328
|
+
prefix (str): 接頭語
|
|
329
|
+
excludes (list): 除外するモジュール名のリスト
|
|
330
|
+
appcls (Any): アプリケーションクラス
|
|
331
|
+
ver (Any): バージョンモジュール
|
|
332
|
+
logger (logging.Logger): ロガー
|
|
333
|
+
isloaded (bool): 読み込み済みかどうか
|
|
334
|
+
"""
|
|
335
|
+
if "svcmd" not in self._options:
|
|
336
|
+
self._options["svcmd"] = dict()
|
|
337
|
+
for mode, f in module.load_features(package_name, prefix, excludes, appcls=appcls, ver=ver).items():
|
|
338
|
+
if mode not in self._options["cmd"]:
|
|
339
|
+
self._options["cmd"][mode] = dict()
|
|
340
|
+
for cmd, opt in f.items():
|
|
341
|
+
self._options["cmd"][mode][cmd] = opt
|
|
342
|
+
fobj:feature.Feature = opt['feature']
|
|
343
|
+
if not isloaded and logger is not None and logger.level == logging.DEBUG:
|
|
344
|
+
logger.debug(f"loaded features: mode={mode}, cmd={cmd}, {fobj}")
|
|
345
|
+
svcmd = fobj.get_svcmd()
|
|
346
|
+
if svcmd is not None:
|
|
347
|
+
self._options["svcmd"][svcmd] = fobj
|
|
348
|
+
self.init_debugoption()
|
|
349
|
+
|
|
350
|
+
def is_features_loaded(self, ftype:str) -> bool:
|
|
351
|
+
"""
|
|
352
|
+
指定されたフィーチャータイプが読み込まれているかどうかを返します。
|
|
353
|
+
|
|
354
|
+
Args:
|
|
355
|
+
ftype (str): フィーチャータイプ
|
|
356
|
+
Returns:
|
|
357
|
+
bool: 読み込まれているかどうか
|
|
358
|
+
"""
|
|
359
|
+
return ftype in self.features_loaded and self.features_loaded[ftype]
|
|
360
|
+
|
|
361
|
+
def load_features_file(self, ftype:str, func, appcls, ver, logger:logging.Logger=None):
|
|
362
|
+
"""
|
|
363
|
+
フィーチャーファイル(features.yml)を読み込みます。
|
|
364
|
+
|
|
365
|
+
Args:
|
|
366
|
+
ftype (str): フィーチャータイプ。cli又はweb
|
|
367
|
+
func (Any): フィーチャーの処理関数
|
|
368
|
+
appcls (Any): アプリケーションクラス
|
|
369
|
+
ver (Any): バージョンモジュール
|
|
370
|
+
logger (logging.Logger): ロガー
|
|
371
|
+
"""
|
|
372
|
+
# 読込み済みかどうかの判定
|
|
373
|
+
if self.is_features_loaded(ftype):
|
|
374
|
+
return
|
|
375
|
+
# cmdboxを拡張したアプリをカスタマイズするときのfeatures.ymlを読み込む
|
|
376
|
+
features_yml = Path(f'.{ver.__appid__}/features.yml')
|
|
377
|
+
if not features_yml.exists() or not features_yml.is_file():
|
|
378
|
+
# cmdboxを拡張したアプリの組み込みfeatures.ymlを読み込む
|
|
379
|
+
features_yml = Path(ver.__file__).parent / 'extensions' / 'features.yml'
|
|
380
|
+
#if not features_yml.exists() or not features_yml.is_file():
|
|
381
|
+
# features_yml = Path('.samples/features.yml')
|
|
382
|
+
logger.info(f"load features.yml: {features_yml}, is_file={features_yml.is_file()}")
|
|
383
|
+
if features_yml.exists() and features_yml.is_file():
|
|
384
|
+
if self.features_yml_data is None:
|
|
385
|
+
self.features_yml_data = yml = common.load_yml(features_yml)
|
|
386
|
+
if logger.level == logging.DEBUG:
|
|
387
|
+
logger.debug(f"features.yml data: {yml}")
|
|
388
|
+
else:
|
|
389
|
+
yml = self.features_yml_data
|
|
390
|
+
if yml is None: return
|
|
391
|
+
if 'features' not in yml:
|
|
392
|
+
raise Exception('features.yml is invalid. (The root element must be "features".)')
|
|
393
|
+
if ftype not in yml['features']:
|
|
394
|
+
raise Exception(f'features.yml is invalid. (There is no “{ftype}” in the “features” element.)')
|
|
395
|
+
if yml['features'][ftype] is None:
|
|
396
|
+
return
|
|
397
|
+
if type(yml['features'][ftype]) is not list:
|
|
398
|
+
raise Exception(f'features.yml is invalid. (The “features.{ftype} element must be a list. {ftype}={yml["features"][ftype]})')
|
|
399
|
+
for data in yml['features'][ftype]:
|
|
400
|
+
if type(data) is not dict:
|
|
401
|
+
raise Exception(f'features.yml is invalid. (The “features.{ftype}” element must be a list element must be a dictionary. data={data})')
|
|
402
|
+
if 'package' not in data:
|
|
403
|
+
raise Exception(f'features.yml is invalid. (The “package” element must be in the dictionary of the list element of the “features.{ftype}” element. data={data})')
|
|
404
|
+
if 'prefix' not in data:
|
|
405
|
+
raise Exception(f'features.yml is invalid. (The prefix element must be in the dictionary of the list element of the “features.{ftype}” element. data={data})')
|
|
406
|
+
if data['package'] is None or data['package'] == "":
|
|
407
|
+
continue
|
|
408
|
+
if data['prefix'] is None or data['prefix'] == "":
|
|
409
|
+
continue
|
|
410
|
+
exclude_modules = []
|
|
411
|
+
if 'exclude_modules' in data:
|
|
412
|
+
if type(data['exclude_modules']) is not list:
|
|
413
|
+
raise Exception(f'features.yml is invalid. (The “exclude_modules” element must be a list element. data={data})')
|
|
414
|
+
exclude_modules = data['exclude_modules']
|
|
415
|
+
func(data['package'], data['prefix'], exclude_modules, appcls, ver, logger, self.is_features_loaded(ftype))
|
|
416
|
+
self.features_loaded[ftype] = True
|
|
417
|
+
|
|
418
|
+
def load_features_args(self, args_dict:Dict[str, Any]):
|
|
419
|
+
yml = self.features_yml_data
|
|
420
|
+
if yml is None:
|
|
421
|
+
return
|
|
422
|
+
if 'args' not in yml or 'cli' not in yml['args']:
|
|
423
|
+
return
|
|
424
|
+
|
|
425
|
+
opts = self.list_options()
|
|
426
|
+
def _cast(self, key, val):
|
|
427
|
+
for opt in opts.values():
|
|
428
|
+
if f"--{key}" in opt['opts']:
|
|
429
|
+
if opt['type'] == int:
|
|
430
|
+
return int(val)
|
|
431
|
+
elif opt['type'] == float:
|
|
432
|
+
return float(val)
|
|
433
|
+
elif opt['type'] == bool:
|
|
434
|
+
return True
|
|
435
|
+
else:
|
|
436
|
+
return eval(val)
|
|
437
|
+
return None
|
|
438
|
+
|
|
439
|
+
for rule in yml['args']['cli']:
|
|
440
|
+
if type(rule) is not dict:
|
|
441
|
+
raise Exception(f'features.yml is invalid. (The “args.cli” element must be a list element must be a dictionary. rule={rule})')
|
|
442
|
+
if 'rule' not in rule:
|
|
443
|
+
raise Exception(f'features.yml is invalid. (The “rule” element must be in the dictionary of the list element of the “args.cli” element. rule={rule})')
|
|
444
|
+
if rule['rule'] is None:
|
|
445
|
+
continue
|
|
446
|
+
if 'default' not in rule and 'coercion' not in rule:
|
|
447
|
+
raise Exception(f'features.yml is invalid. (The “default” or “coercion” element must be in the dictionary of the list element of the “args.cli” element. rule={rule})')
|
|
448
|
+
if len([rk for rk in rule['rule'] if rk not in args_dict or rule['rule'][rk] != args_dict[rk]]) > 0:
|
|
449
|
+
continue
|
|
450
|
+
if 'default' in rule and rule['default'] is not None:
|
|
451
|
+
for dk, dv in rule['default'].items():
|
|
452
|
+
if dk not in args_dict or args_dict[dk] is None:
|
|
453
|
+
if type(dv) == list:
|
|
454
|
+
args_dict[dk] = [_cast(self, dk, v) for v in dv]
|
|
455
|
+
else:
|
|
456
|
+
args_dict[dk] = _cast(self, dk, dv)
|
|
457
|
+
if 'coercion' in rule and rule['coercion'] is not None:
|
|
458
|
+
for ck, cv in rule['coercion'].items():
|
|
459
|
+
if type(cv) == list:
|
|
460
|
+
args_dict[ck] = [_cast(self, ck, v) for v in cv]
|
|
461
|
+
else:
|
|
462
|
+
args_dict[ck] = _cast(self, ck, cv)
|
|
463
|
+
|
|
464
|
+
def load_features_aliases_cli(self, logger:logging.Logger):
|
|
465
|
+
yml = self.features_yml_data
|
|
466
|
+
if yml is None: return
|
|
467
|
+
if self.aliases_loaded_cli: return
|
|
468
|
+
if 'aliases' not in yml or 'cli' not in yml['aliases']:
|
|
469
|
+
return
|
|
470
|
+
|
|
471
|
+
opt_cmd = self._options["cmd"].copy()
|
|
472
|
+
for rule in yml['aliases']['cli']:
|
|
473
|
+
if type(rule) is not dict:
|
|
474
|
+
raise Exception(f'features.yml is invalid. (The aliases.cli” element must be a list element must be a dictionary. rule={rule})')
|
|
475
|
+
if 'source' not in rule:
|
|
476
|
+
raise Exception(f'features.yml is invalid. (The source element must be in the dictionary of the list element of the aliases.cli” element. rule={rule})')
|
|
477
|
+
if 'target' not in rule:
|
|
478
|
+
raise Exception(f'features.yml is invalid. (The target element must be in the dictionary of the list element of the aliases.cli” element. rule={rule})')
|
|
479
|
+
if rule['source'] is None or rule['target'] is None:
|
|
480
|
+
if logger.level == logging.DEBUG:
|
|
481
|
+
logger.debug(f'Skip cli rule in features.yml. (The source or target element is None. rule={rule})')
|
|
482
|
+
continue
|
|
483
|
+
if type(rule['source']) is not dict:
|
|
484
|
+
raise Exception(f'features.yml is invalid. (The aliases.cli.source” element must be a dictionary element must. rule={rule})')
|
|
485
|
+
if type(rule['target']) is not dict:
|
|
486
|
+
raise Exception(f'features.yml is invalid. (The aliases.cli.target element must be a dictionary element must. rule={rule})')
|
|
487
|
+
if 'mode' not in rule['source'] or 'cmd' not in rule['source']:
|
|
488
|
+
raise Exception(f'features.yml is invalid. (The aliases.cli.source element must have "mode" and "cmd" specified. rule={rule})')
|
|
489
|
+
if 'mode' not in rule['target'] or 'cmd' not in rule['target']:
|
|
490
|
+
raise Exception(f'features.yml is invalid. (The aliases.cli.target element must have "mode" and "cmd" specified. rule={rule})')
|
|
491
|
+
if rule['source']['mode'] is None or rule['source']['cmd'] is None:
|
|
492
|
+
if logger.level == logging.DEBUG:
|
|
493
|
+
logger.debug(f'Skip cli rule in features.yml. (The source mode or cmd element is None. rule={rule})')
|
|
494
|
+
continue
|
|
495
|
+
if rule['target']['mode'] is None or rule['target']['cmd'] is None:
|
|
496
|
+
if logger.level == logging.DEBUG:
|
|
497
|
+
logger.debug(f'Skip cli rule in features.yml. (The target mode or cmd element is None. rule={rule})')
|
|
498
|
+
continue
|
|
499
|
+
tgt_move = True if 'move' in rule['target'] and rule['target']['move'] else False
|
|
500
|
+
reg_src_cmd = re.compile(rule['source']['cmd'])
|
|
501
|
+
for mk, mv in opt_cmd.items():
|
|
502
|
+
if type(mv) is not dict: continue
|
|
503
|
+
if mk != rule['source']['mode']: continue
|
|
504
|
+
src_mode = mk
|
|
505
|
+
tgt_mode = rule['target']['mode']
|
|
506
|
+
self._options["cmd"][tgt_mode] = dict() if tgt_mode not in self._options["cmd"] else self._options["cmd"][tgt_mode]
|
|
507
|
+
self._options["mode"][tgt_mode] = dict() if tgt_mode not in self._options["mode"] else self._options["mode"][tgt_mode]
|
|
508
|
+
find = False
|
|
509
|
+
for ck, cv in mv.copy().items():
|
|
510
|
+
if type(cv) is not dict: continue
|
|
511
|
+
ck_match:re.Match = reg_src_cmd.search(ck)
|
|
512
|
+
if ck_match is None: continue
|
|
513
|
+
find = True
|
|
514
|
+
src_cmd = ck
|
|
515
|
+
tgt_cmd = rule['target']['cmd'].format(*([ck_match.string]+list(ck_match.groups())))
|
|
516
|
+
cv = cv.copy()
|
|
517
|
+
cv['opt'] = tgt_cmd
|
|
518
|
+
# cmd/[target mode]/[target cmd]に追加
|
|
519
|
+
self._options["cmd"][tgt_mode][tgt_cmd] = cv
|
|
520
|
+
# mode/[target mode]/[target cmd]に追加
|
|
521
|
+
self._options["mode"][tgt_mode][tgt_cmd] = cv
|
|
522
|
+
# mode/choiceにtarget modeがない場合は追加
|
|
523
|
+
found_mode_choice = False
|
|
524
|
+
for i, me in enumerate(self._options["mode"]["choice"]):
|
|
525
|
+
if me['opt'] == tgt_mode:
|
|
526
|
+
me[tgt_cmd] = cv.copy()
|
|
527
|
+
found_mode_choice = True
|
|
528
|
+
# 移動の場合は元を削除
|
|
529
|
+
if tgt_move and me['opt'] == src_mode and src_cmd in me:
|
|
530
|
+
del me[src_cmd]
|
|
531
|
+
if not found_mode_choice:
|
|
532
|
+
self._options["mode"]["choice"].append({'opt':tgt_mode, tgt_cmd:cv})
|
|
533
|
+
# cmd/choiceにtarget cmdがない場合は追加
|
|
534
|
+
found_cmd_choice = False
|
|
535
|
+
for i, ce in enumerate(self._options["cmd"]["choice"]):
|
|
536
|
+
if ce['opt'] == tgt_cmd:
|
|
537
|
+
self._options["cmd"]["choice"][i] = cv
|
|
538
|
+
found_cmd_choice = True
|
|
539
|
+
# 移動の場合は元を削除(この処理をするとモード違いの同名コマンドが使えなくなるのでコメントアウト)
|
|
540
|
+
#if tgt_move and ce['opt'] == src_cmd:
|
|
541
|
+
# self._options["cmd"]["choice"].remove(ce)
|
|
542
|
+
if not found_cmd_choice:
|
|
543
|
+
self._options["cmd"]["choice"].append(cv)
|
|
544
|
+
# 移動の場合は元を削除
|
|
545
|
+
if tgt_move:
|
|
546
|
+
if logger.level == logging.DEBUG:
|
|
547
|
+
logger.debug(f'move command: src=({src_mode},{src_cmd}) -> tgt=({tgt_mode},{tgt_cmd})')
|
|
548
|
+
if src_cmd in self._options["cmd"][src_mode]:
|
|
549
|
+
del self._options["cmd"][src_mode][src_cmd]
|
|
550
|
+
else:
|
|
551
|
+
if logger.level == logging.DEBUG:
|
|
552
|
+
logger.debug(f'copy command: src=({src_mode},{src_cmd}) -> tgt=({tgt_mode},{tgt_cmd})')
|
|
553
|
+
if not find:
|
|
554
|
+
logger.warning(f'Skip cli rule in features.yml. (Command matching the rule not found. rule={rule})')
|
|
555
|
+
if len(self._options["cmd"][src_mode]) == 1:
|
|
556
|
+
del self._options["cmd"][src_mode]
|
|
557
|
+
if len(self._options["mode"][src_mode]) == 1:
|
|
558
|
+
del self._options["mode"][src_mode]
|
|
559
|
+
self.aliases_loaded_cli = True
|
|
560
|
+
|
|
561
|
+
def load_features_aliases_web(self, routes:List[Route], logger:logging.Logger):
|
|
562
|
+
yml = self.features_yml_data
|
|
563
|
+
if yml is None: return
|
|
564
|
+
if self.aliases_loaded_web: return
|
|
565
|
+
if routes is None or type(routes) is not list or len(routes) == 0:
|
|
566
|
+
raise Exception(f'routes is invalid. (The routes must be a list element.) routes={routes}')
|
|
567
|
+
if 'aliases' not in yml or 'web' not in yml['aliases']:
|
|
568
|
+
return
|
|
569
|
+
|
|
570
|
+
for rule in yml['aliases']['web']:
|
|
571
|
+
if type(rule) is not dict:
|
|
572
|
+
raise Exception(f'features.yml is invalid. (The aliases.web element must be a list element must be a dictionary. rule={rule})')
|
|
573
|
+
if 'source' not in rule:
|
|
574
|
+
raise Exception(f'features.yml is invalid. (The source element must be in the dictionary of the list element of the aliases.web element. rule={rule})')
|
|
575
|
+
if 'target' not in rule:
|
|
576
|
+
raise Exception(f'features.yml is invalid. (The target element must be in the dictionary of the list element of the aliases.web element. rule={rule})')
|
|
577
|
+
if rule['source'] is None or rule['target'] is None:
|
|
578
|
+
if logger.level == logging.DEBUG:
|
|
579
|
+
logger.debug(f'Skip web rule in features.yml. (The source or target element is None. rule={rule})')
|
|
580
|
+
continue
|
|
581
|
+
if type(rule['source']) is not dict:
|
|
582
|
+
raise Exception(f'features.yml is invalid. (The aliases.web.source” element must be a dictionary element must. rule={rule})')
|
|
583
|
+
if type(rule['target']) is not dict:
|
|
584
|
+
raise Exception(f'features.yml is invalid. (The aliases.web.target element must be a dictionary element must. rule={rule})')
|
|
585
|
+
if 'path' not in rule['source']:
|
|
586
|
+
raise Exception(f'features.yml is invalid. (The aliases.web.source element must have "path" specified. rule={rule})')
|
|
587
|
+
if 'path' not in rule['target']:
|
|
588
|
+
raise Exception(f'features.yml is invalid. (The aliases.web.target element must have "path" specified. rule={rule})')
|
|
589
|
+
if rule['source']['path'] is None:
|
|
590
|
+
if logger.level == logging.DEBUG:
|
|
591
|
+
logger.debug(f'Skip web rule in features.yml. (The source path element is None. rule={rule})')
|
|
592
|
+
continue
|
|
593
|
+
if rule['target']['path'] is None:
|
|
594
|
+
if logger.level == logging.DEBUG:
|
|
595
|
+
logger.debug(f'Skip web rule in features.yml. (The target path element is None. rule={rule})')
|
|
596
|
+
continue
|
|
597
|
+
tgt_move = True if 'move' in rule['target'] and rule['target']['move'] else False
|
|
598
|
+
reg_src_path = re.compile(rule['source']['path'])
|
|
599
|
+
find = False
|
|
600
|
+
for route in routes.copy():
|
|
601
|
+
if not isinstance(route, APIRoute):
|
|
602
|
+
continue
|
|
603
|
+
route_path = route.path
|
|
604
|
+
path_match:re.Match = reg_src_path.search(route_path)
|
|
605
|
+
if path_match is None: continue
|
|
606
|
+
find = True
|
|
607
|
+
tgt_Path = rule['target']['path'].format(*([path_match.string]+list(path_match.groups())))
|
|
608
|
+
tgt_route = APIRoute(tgt_Path, route.endpoint, methods=route.methods, name=route.name,
|
|
609
|
+
include_in_schema=route.include_in_schema)
|
|
610
|
+
routes.append(tgt_route)
|
|
611
|
+
if tgt_move:
|
|
612
|
+
if logger.level == logging.DEBUG:
|
|
613
|
+
logger.debug(f'move route: src=({route_path}) -> tgt=({tgt_Path})')
|
|
614
|
+
routes.remove(route)
|
|
615
|
+
else:
|
|
616
|
+
if logger.level == logging.DEBUG:
|
|
617
|
+
logger.debug(f'copy route: src=({route_path}) -> tgt=({tgt_Path})')
|
|
618
|
+
if not find:
|
|
619
|
+
logger.warning(f'Skip web rule in features.yml. (Command matching the rule not found. rule={rule})')
|
|
620
|
+
self.aliases_loaded_web = True
|
|
621
|
+
|
|
622
|
+
def load_features_audit(self, logger:logging.Logger):
|
|
623
|
+
yml = self.features_yml_data
|
|
624
|
+
if yml is None: return
|
|
625
|
+
if self.audit_loaded: return
|
|
626
|
+
if 'audit' not in yml: return
|
|
627
|
+
if 'enabled' not in yml['audit']:
|
|
628
|
+
raise Exception('features.yml is invalid. (The audit element must have "enabled" specified.)')
|
|
629
|
+
if not yml['audit']['enabled']: return
|
|
630
|
+
# writeフューチャー
|
|
631
|
+
if 'write' not in yml['audit']:
|
|
632
|
+
raise Exception('features.yml is invalid. (The audit element must have "write" specified.)')
|
|
633
|
+
if 'mode' not in yml['audit']['write']:
|
|
634
|
+
raise Exception('features.yml is invalid. (The audit.write element must have "mode" specified.)')
|
|
635
|
+
mode = yml['audit']['write']['mode']
|
|
636
|
+
if 'cmd' not in yml['audit']['write']:
|
|
637
|
+
raise Exception('features.yml is invalid. (The audit.write element must have "cmd" specified.)')
|
|
638
|
+
cmd = yml['audit']['write']['cmd']
|
|
639
|
+
self.audit_write:feature.Feature = self.get_cmd_attr(mode, cmd, 'feature')
|
|
640
|
+
# searchフューチャー
|
|
641
|
+
if 'search' not in yml['audit']:
|
|
642
|
+
raise Exception('features.yml is invalid. (The audit element must have "search" specified.)')
|
|
643
|
+
if 'mode' not in yml['audit']['search']:
|
|
644
|
+
raise Exception('features.yml is invalid. (The audit.search element must have "mode" specified.)')
|
|
645
|
+
mode = yml['audit']['search']['mode']
|
|
646
|
+
if 'cmd' not in yml['audit']['search']:
|
|
647
|
+
raise Exception('features.yml is invalid. (The audit.search element must have "cmd" specified.)')
|
|
648
|
+
cmd = yml['audit']['search']['cmd']
|
|
649
|
+
self.audit_search:feature.Feature = self.get_cmd_attr(mode, cmd, 'feature')
|
|
650
|
+
# フューチャーのoptions
|
|
651
|
+
if 'options' not in yml['audit']:
|
|
652
|
+
raise Exception('features.yml is invalid. (The audit element must have "options" specified.)')
|
|
653
|
+
self.audit_write_args = yml['audit']['options'].copy()
|
|
654
|
+
self.audit_write_args['mode'] = mode
|
|
655
|
+
self.audit_write_args['cmd'] = cmd
|
|
656
|
+
self.audit_search_args = yml['audit']['options'].copy()
|
|
657
|
+
self.audit_search_args['mode'] = mode
|
|
658
|
+
self.audit_search_args['cmd'] = cmd
|
|
659
|
+
self.audit_loaded = True
|
|
660
|
+
|
|
661
|
+
AT_USER = 'user'
|
|
662
|
+
AT_ADMIN = 'admin'
|
|
663
|
+
AT_SYSTEM = 'system'
|
|
664
|
+
AT_AUTH = 'auth'
|
|
665
|
+
AT_EVENT = 'event'
|
|
666
|
+
AUDITS = [AT_USER, AT_ADMIN, AT_SYSTEM, AT_AUTH, AT_EVENT]
|
|
667
|
+
|
|
668
|
+
@staticmethod
|
|
669
|
+
def audit(body:Dict[str, Any]=None, audit_type:str=None, tags:List[str]=None, src:str=None) -> int:
|
|
670
|
+
"""
|
|
671
|
+
監査ログを書き込む関数を返します。
|
|
672
|
+
デコレーターとして使用することができます。
|
|
673
|
+
|
|
674
|
+
Args:
|
|
675
|
+
body (Dict[str, Any]): 監査ログの内容
|
|
676
|
+
audit_type (str): 監査の種類
|
|
677
|
+
tags (List[str]): メッセージのタグ
|
|
678
|
+
src (str): メッセージの発生源
|
|
679
|
+
|
|
680
|
+
Returns:
|
|
681
|
+
int: レスポンスコード
|
|
682
|
+
"""
|
|
683
|
+
self = Options.getInstance()
|
|
684
|
+
if body is None:
|
|
685
|
+
body = dict()
|
|
686
|
+
if body is not None and type(body) is not dict:
|
|
687
|
+
raise Exception('body is invalid. (The body must be a dictionary element.)')
|
|
688
|
+
if audit_type is not None and audit_type not in Options.AUDITS:
|
|
689
|
+
raise Exception(f'audit_type is invalid. (The audit_type must be one of the following: {Options.AUDITS})')
|
|
690
|
+
tags = tags if tags is not None else []
|
|
691
|
+
if tags is not None and type(tags) is not list:
|
|
692
|
+
raise Exception('clmsg_tags is invalid. (The clmsg_tags must be a list element.)')
|
|
693
|
+
def _audit_write(func):
|
|
694
|
+
@functools.wraps(func)
|
|
695
|
+
def _wrapper(*args, **kwargs):
|
|
696
|
+
self.audit_exec(*args, body=body, audit_type=audit_type, tags=tags, src=src, **kwargs)
|
|
697
|
+
ret = func(*args, **kwargs)
|
|
698
|
+
return ret
|
|
699
|
+
return _wrapper
|
|
700
|
+
return _audit_write
|
|
701
|
+
|
|
702
|
+
def audit_exec(self, *args, body:Dict[str, Any]=None, audit_type:str=None, tags:List[str]=None, src:str=None, title:str=None, user:str=None, **kwargs) -> None:
|
|
703
|
+
"""
|
|
704
|
+
監査ログを書き込みます。
|
|
705
|
+
|
|
706
|
+
Args:
|
|
707
|
+
args (Any): 呼び出し元で使用している引数
|
|
708
|
+
body (Dict[str, Any]): 監査ログの内容
|
|
709
|
+
audit_type (str): 監査の種類
|
|
710
|
+
tags (List[str]): メッセージのタグ
|
|
711
|
+
src (str): メッセージの発生源
|
|
712
|
+
title (str): メッセージのタイトル
|
|
713
|
+
user (str): メッセージを発生させたユーザー名
|
|
714
|
+
kwargs (Any): 呼び出し元で使用しているキーワード引数
|
|
715
|
+
"""
|
|
716
|
+
if not hasattr(self, 'audit_write') or self.audit_write is None:
|
|
717
|
+
raise Exception('audit write feature is not found.')
|
|
718
|
+
clmsg_date = datetime.now().strftime('%Y-%m-%d %H:%M:%S') + common.get_tzoffset_str()
|
|
719
|
+
opt = self.audit_write_args.copy()
|
|
720
|
+
opt['audit_type'] = audit_type
|
|
721
|
+
opt['clmsg_id'] = str(uuid.uuid4())
|
|
722
|
+
opt['clmsg_date'] = clmsg_date
|
|
723
|
+
opt['clmsg_src'] = opt['clmsg_src'] if 'clmsg_src' in opt else None
|
|
724
|
+
opt['clmsg_title'] = opt['clmsg_title'] if 'clmsg_title' in opt else None
|
|
725
|
+
opt['clmsg_user'] = user
|
|
726
|
+
opt['clmsg_tag'] = tags
|
|
727
|
+
opt['format'] = False if opt.get('format') is None else opt['format']
|
|
728
|
+
opt['output_json'] = None if opt.get('output_json') is None else opt['output_json']
|
|
729
|
+
opt['output_json_append'] = False if opt.get('output_json_append') is None else opt['output_json_append']
|
|
730
|
+
opt['host'] = 'localhost' if opt.get('host') is None else opt['host']
|
|
731
|
+
opt['port'] = 6379 if opt.get('port') is None else opt['port']
|
|
732
|
+
opt['password'] = 'password' if opt.get('password') is None else opt['password']
|
|
733
|
+
opt['svname'] = 'server' if opt.get('svname') is None else opt['svname']
|
|
734
|
+
opt['retry_count'] = 1 if opt.get('retry_count') is None else opt['retry_count']
|
|
735
|
+
opt['retry_interval'] = 1 if opt.get('retry_interval') is None else opt['retry_interval']
|
|
736
|
+
opt['timeout'] = 5 if opt.get('timeout') is None else opt['timeout']
|
|
737
|
+
opt['pg_enabled'] = False if opt.get('pg_enabled') is None else opt['pg_enabled']
|
|
738
|
+
opt['pg_host'] = 'localhost' if opt.get('pg_host') is None else opt['pg_host']
|
|
739
|
+
opt['pg_port'] = 5432 if opt.get('pg_port') is None else opt['pg_port']
|
|
740
|
+
opt['pg_user'] = 'postgres' if opt.get('pg_user') is None else opt['pg_user']
|
|
741
|
+
opt['pg_password'] = 'postgres' if opt.get('pg_password') is None else opt['pg_password']
|
|
742
|
+
opt['pg_dbname'] = 'audit' if opt.get('pg_dbname') is None else opt['pg_dbname']
|
|
743
|
+
logger = self.default_logger
|
|
744
|
+
clmsg_body = body.copy() if body is not None else dict()
|
|
745
|
+
func_feature = None
|
|
746
|
+
for arg in list(args) + list(kwargs.values()):
|
|
747
|
+
if isinstance(arg, logging.Logger): logger = arg
|
|
748
|
+
elif isinstance(arg, argparse.Namespace):
|
|
749
|
+
mode = arg.mode if hasattr(arg, 'mode') else None
|
|
750
|
+
cmd = arg.cmd if hasattr(arg, 'cmd') else None
|
|
751
|
+
if mode is not None and cmd is not None:
|
|
752
|
+
opt_schema = self.get_cmd_choices(mode, cmd, True)
|
|
753
|
+
for key, val in arg.__dict__.items():
|
|
754
|
+
if key in ['stdout_log', 'capture_stdout']:
|
|
755
|
+
continue
|
|
756
|
+
schema = [schema for schema in opt_schema if type(schema) is dict and schema['opt'] == key]
|
|
757
|
+
if len(schema) == 0 or val == '' or val is None:
|
|
758
|
+
continue
|
|
759
|
+
if 'web' in schema[0] and schema[0]['web'] == 'mask':
|
|
760
|
+
clmsg_body[key] = '********'
|
|
761
|
+
else:
|
|
762
|
+
clmsg_body[key] = common.to_str(val, 100)
|
|
763
|
+
opt[key] = val
|
|
764
|
+
if hasattr(arg, 'clmsg_id'): opt['clmsg_id'] = arg.clmsg_id
|
|
765
|
+
elif isinstance(arg, web.Web):
|
|
766
|
+
opt['host'] = arg.redis_host
|
|
767
|
+
opt['port'] = arg.redis_port
|
|
768
|
+
opt['password'] = arg.redis_password
|
|
769
|
+
opt['svname'] = arg.svname
|
|
770
|
+
elif isinstance(arg, feature.Feature):
|
|
771
|
+
func_feature = arg
|
|
772
|
+
opt['clmsg_src'] = func_feature.__class__.__name__
|
|
773
|
+
elif isinstance(arg, Request):
|
|
774
|
+
if 'signin' in arg.session and arg.session['signin'] is not None and 'name' in arg.session['signin']:
|
|
775
|
+
opt['clmsg_user'] = arg.session['signin']['name']
|
|
776
|
+
if opt['audit_type'] is None:
|
|
777
|
+
opt['audit_type'] = Options.AT_ADMIN if 'admin' in arg.session['signin']['groups'] else Options.AT_USER
|
|
778
|
+
opt['clmsg_id'] = arg.session['signin']['clmsg_id'] if 'clmsg_id' in arg.session['signin'] else opt['clmsg_id']
|
|
779
|
+
arg.session['signin']['clmsg_id'] = opt['clmsg_id']
|
|
780
|
+
opt['clmsg_src'] = arg.url.path
|
|
781
|
+
opt['clmsg_body'] = clmsg_body
|
|
782
|
+
opt['audit_type'] = opt['audit_type'] if opt['audit_type'] else Options.AT_EVENT
|
|
783
|
+
if src is not None and src != "":
|
|
784
|
+
opt['clmsg_src'] = src
|
|
785
|
+
if title is not None and title != "":
|
|
786
|
+
opt['clmsg_title'] = title
|
|
787
|
+
audit_write_args = argparse.Namespace(**{k:common.chopdq(v) for k,v in opt.items()})
|
|
788
|
+
self.audit_write.apprun(logger, audit_write_args, tm=0.0, pf=[])
|