stoobly-agent 1.3.0__py3-none-any.whl → 1.4.0__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.
- stoobly_agent/__init__.py +1 -1
- stoobly_agent/app/api/application_http_request_handler.py +3 -3
- stoobly_agent/app/api/proxy_controller.py +8 -7
- stoobly_agent/app/cli/config_cli.py +1 -1
- stoobly_agent/app/cli/helpers/certificate_authority.py +6 -1
- stoobly_agent/app/cli/helpers/print_service.py +17 -0
- stoobly_agent/app/cli/scaffold/app.py +2 -2
- stoobly_agent/app/cli/scaffold/constants.py +0 -2
- stoobly_agent/app/cli/scaffold/hosts_file_manager.py +112 -0
- stoobly_agent/app/cli/scaffold/service.py +0 -1
- stoobly_agent/app/cli/scaffold/service_config.py +10 -14
- stoobly_agent/app/cli/scaffold/service_workflow_validate_command.py +3 -3
- stoobly_agent/app/cli/scaffold/templates/app/.Dockerfile.context +1 -1
- stoobly_agent/app/cli/scaffold/templates/app/.Makefile +77 -53
- stoobly_agent/app/cli/scaffold/templates/app/stoobly-ui/exec/bin/.services +9 -0
- stoobly_agent/app/cli/scaffold/workflow_create_command.py +0 -1
- stoobly_agent/app/cli/scaffold/workflow_run_command.py +6 -9
- stoobly_agent/app/cli/scaffold_cli.py +200 -69
- stoobly_agent/app/cli/snapshot_cli.py +1 -1
- stoobly_agent/app/proxy/handle_mock_service.py +2 -0
- stoobly_agent/app/proxy/handle_replay_service.py +2 -0
- stoobly_agent/app/proxy/mitmproxy/request_facade.py +1 -1
- stoobly_agent/app/proxy/mitmproxy/response_body_facade.py +19 -0
- stoobly_agent/app/proxy/mitmproxy/response_facade.py +90 -18
- stoobly_agent/app/proxy/record/join_request_service.py +1 -1
- stoobly_agent/app/settings/constants/request_component.py +2 -1
- stoobly_agent/config/constants/custom_headers.py +13 -13
- stoobly_agent/config/constants/headers.py +0 -2
- stoobly_agent/public/18-es2015.583f191cc7ad512ee262.js +1 -0
- stoobly_agent/public/18-es5.583f191cc7ad512ee262.js +1 -0
- stoobly_agent/public/35-es2015.8f79ff8748d4ff06ab03.js +1 -0
- stoobly_agent/public/35-es5.8f79ff8748d4ff06ab03.js +1 -0
- stoobly_agent/public/index.html +1 -1
- stoobly_agent/public/main-es2015.2cc16523aa3fcaba51e5.js +1 -0
- stoobly_agent/public/main-es5.2cc16523aa3fcaba51e5.js +1 -0
- stoobly_agent/public/{runtime-es2015.9addf49b79aca951b7e2.js → runtime-es2015.b914470164e4d6e75d96.js} +1 -1
- stoobly_agent/public/{runtime-es5.9addf49b79aca951b7e2.js → runtime-es5.b914470164e4d6e75d96.js} +1 -1
- stoobly_agent/test/app/cli/scaffold/{hosts_file_reader_test.py → hosts_file_manager_test.py} +20 -20
- stoobly_agent/test/app/models/schemas/.stoobly/db/VERSION +1 -1
- {stoobly_agent-1.3.0.dist-info → stoobly_agent-1.4.0.dist-info}/METADATA +1 -1
- {stoobly_agent-1.3.0.dist-info → stoobly_agent-1.4.0.dist-info}/RECORD +44 -42
- stoobly_agent/app/cli/scaffold/hosts_file_reader.py +0 -65
- stoobly_agent/public/18-es2015.d3b430636a4d6f544d92.js +0 -1
- stoobly_agent/public/18-es5.d3b430636a4d6f544d92.js +0 -1
- stoobly_agent/public/35-es2015.f741ebce0bfc25f0ec99.js +0 -1
- stoobly_agent/public/35-es5.f741ebce0bfc25f0ec99.js +0 -1
- stoobly_agent/public/main-es2015.ccd46ac1b6638ddf2066.js +0 -1
- stoobly_agent/public/main-es5.ccd46ac1b6638ddf2066.js +0 -1
- {stoobly_agent-1.3.0.dist-info → stoobly_agent-1.4.0.dist-info}/LICENSE +0 -0
- {stoobly_agent-1.3.0.dist-info → stoobly_agent-1.4.0.dist-info}/WHEEL +0 -0
- {stoobly_agent-1.3.0.dist-info → stoobly_agent-1.4.0.dist-info}/entry_points.txt +0 -0
@@ -1,4 +1,5 @@
|
|
1
1
|
import click
|
2
|
+
import errno
|
2
3
|
import os
|
3
4
|
import pdb
|
4
5
|
import sys
|
@@ -14,6 +15,7 @@ from stoobly_agent.app.cli.scaffold.constants import (
|
|
14
15
|
)
|
15
16
|
from stoobly_agent.app.cli.scaffold.docker.service.set_gateway_ports import set_gateway_ports
|
16
17
|
from stoobly_agent.app.cli.scaffold.docker.workflow.decorators_factory import get_workflow_decorators
|
18
|
+
from stoobly_agent.app.cli.scaffold.hosts_file_manager import HostsFileManager
|
17
19
|
from stoobly_agent.app.cli.scaffold.service import Service
|
18
20
|
from stoobly_agent.app.cli.scaffold.service_config import ServiceConfig
|
19
21
|
from stoobly_agent.app.cli.scaffold.service_create_command import ServiceCreateCommand
|
@@ -31,8 +33,13 @@ from stoobly_agent.config.constants import env_vars
|
|
31
33
|
from stoobly_agent.config.data_dir import DataDir
|
32
34
|
from stoobly_agent.lib.logger import bcolors, DEBUG, ERROR, INFO, Logger, WARNING
|
33
35
|
|
36
|
+
from .helpers.print_service import FORMATS, print_services, select_print_options
|
37
|
+
|
34
38
|
LOG_ID = 'Scaffold'
|
35
39
|
|
40
|
+
current_working_dir = os.getcwd()
|
41
|
+
data_dir: DataDir = DataDir.instance()
|
42
|
+
|
36
43
|
@click.group(
|
37
44
|
epilog="Run 'stoobly-agent project COMMAND --help' for more information on a command.",
|
38
45
|
help="Manage scaffold"
|
@@ -57,10 +64,26 @@ def app(ctx):
|
|
57
64
|
def service(ctx):
|
58
65
|
pass
|
59
66
|
|
67
|
+
@click.group(
|
68
|
+
epilog="Run 'stoobly-agent request response COMMAND --help' for more information on a command.",
|
69
|
+
help="Manage workflow scaffold"
|
70
|
+
)
|
71
|
+
@click.pass_context
|
72
|
+
def workflow(ctx):
|
73
|
+
pass
|
74
|
+
|
75
|
+
@click.group(
|
76
|
+
epilog="Run 'stoobly-agent scaffold hostname COMMAND --help' for more information on a command.",
|
77
|
+
help="Manage scaffold service hostnames"
|
78
|
+
)
|
79
|
+
@click.pass_context
|
80
|
+
def hostname(ctx):
|
81
|
+
pass
|
82
|
+
|
60
83
|
@app.command(
|
61
84
|
help="Scaffold application"
|
62
85
|
)
|
63
|
-
@click.option('--app-dir-path', default=
|
86
|
+
@click.option('--app-dir-path', default=current_working_dir, help='Path to create the app scaffold.')
|
64
87
|
@click.option('--force', is_flag=True, help='Overwrite maintained scaffolded app files.')
|
65
88
|
@click.option('--network', help='App default network name. Defaults to app name.')
|
66
89
|
@click.argument('app_name')
|
@@ -80,17 +103,14 @@ def create(**kwargs):
|
|
80
103
|
@app.command(
|
81
104
|
help="Scaffold app service certs"
|
82
105
|
)
|
83
|
-
@click.option('--app-dir-path', default=
|
84
|
-
@click.option('--ca-certs-dir-path', default=
|
106
|
+
@click.option('--app-dir-path', default=current_working_dir, help='Path to application directory.')
|
107
|
+
@click.option('--ca-certs-dir-path', default=data_dir.mitmproxy_conf_dir_path, help='Path to ca certs directory used to sign SSL certs. Defaults to ~/.mitmproxy')
|
85
108
|
@click.option('--certs-dir-path', help='Path to certs directory. Defaults to the certs dir of the context.')
|
86
|
-
@click.option('--context-dir-path', default=
|
109
|
+
@click.option('--context-dir-path', default=data_dir.context_dir_path, help='Path to Stoobly data directory.')
|
87
110
|
@click.option('--service', multiple=True, help='Select which services to run. Defaults to all.')
|
88
111
|
def mkcert(**kwargs):
|
89
112
|
app = App(kwargs['app_dir_path'], DOCKER_NAMESPACE, **kwargs)
|
90
|
-
|
91
|
-
if not app.exists:
|
92
|
-
print(f"Error: {app.dir_path} does not exist", file=sys.stderr)
|
93
|
-
sys.exit(1)
|
113
|
+
__validate_app(app)
|
94
114
|
|
95
115
|
services = __get_services(app.services, service=kwargs['service'])
|
96
116
|
|
@@ -114,7 +134,7 @@ def mkcert(**kwargs):
|
|
114
134
|
@service.command(
|
115
135
|
help="Scaffold a service",
|
116
136
|
)
|
117
|
-
@click.option('--app-dir-path', default=
|
137
|
+
@click.option('--app-dir-path', default=current_working_dir, help='Path to application directory.')
|
118
138
|
@click.option('--detached', is_flag=True)
|
119
139
|
@click.option('--env', multiple=True, help='Specify an environment variable.')
|
120
140
|
@click.option('--force', is_flag=True, help='Overwrite maintained scaffolded service files.')
|
@@ -142,9 +162,34 @@ def create(**kwargs):
|
|
142
162
|
print(f"{service.dir_path} already exists, use option '--force' to continue")
|
143
163
|
|
144
164
|
@service.command(
|
145
|
-
help="
|
165
|
+
help="List services",
|
166
|
+
name="list"
|
146
167
|
)
|
147
168
|
@click.option('--app-dir-path', default=os.getcwd(), help='Path to application directory.')
|
169
|
+
@click.option('--format', type=click.Choice(FORMATS), help='Format output.')
|
170
|
+
@click.option('--select', multiple=True, help='Select column(s) to display.')
|
171
|
+
@click.option('--service', multiple=True, help='Select specific services.')
|
172
|
+
@click.option('--without-headers', is_flag=True, default=False, help='Disable printing column headers.')
|
173
|
+
@click.option('--workflow', multiple=True, help='Specify workflow(s) to filter the services by.')
|
174
|
+
def _list(**kwargs):
|
175
|
+
__validate_app_dir(kwargs['app_dir_path'])
|
176
|
+
|
177
|
+
app = App(kwargs['app_dir_path'], DOCKER_NAMESPACE)
|
178
|
+
__validate_app(app)
|
179
|
+
|
180
|
+
rows = []
|
181
|
+
for service_name in __get_workflow_services(app, **kwargs):
|
182
|
+
service = Service(service_name, app)
|
183
|
+
__validate_service_dir(service.dir_path)
|
184
|
+
service_config = ServiceConfig(service.dir_path)
|
185
|
+
rows.append(service_config.to_dict())
|
186
|
+
|
187
|
+
print_services(rows, **select_print_options(kwargs))
|
188
|
+
|
189
|
+
@service.command(
|
190
|
+
help="Delete a service",
|
191
|
+
)
|
192
|
+
@click.option('--app-dir-path', default=current_working_dir, help='Path to application directory.')
|
148
193
|
@click.argument('service_name')
|
149
194
|
def delete(**kwargs):
|
150
195
|
__validate_app_dir(kwargs['app_dir_path'])
|
@@ -162,7 +207,7 @@ def delete(**kwargs):
|
|
162
207
|
@service.command(
|
163
208
|
help="Update a service config"
|
164
209
|
)
|
165
|
-
@click.option('--app-dir-path', default=
|
210
|
+
@click.option('--app-dir-path', default=current_working_dir, help='Path to application directory.')
|
166
211
|
@click.option('--priority', help='Determines the service run order.')
|
167
212
|
@click.argument('service_name')
|
168
213
|
def update(**kwargs):
|
@@ -180,18 +225,11 @@ def update(**kwargs):
|
|
180
225
|
|
181
226
|
service_config.write()
|
182
227
|
|
183
|
-
@click.group(
|
184
|
-
epilog="Run 'stoobly-agent request response COMMAND --help' for more information on a command.",
|
185
|
-
help="Manage service scaffold"
|
186
|
-
)
|
187
|
-
@click.pass_context
|
188
|
-
def workflow(ctx):
|
189
|
-
pass
|
190
|
-
|
191
228
|
@workflow.command(
|
192
229
|
help="Create workflow for service(s)"
|
193
230
|
)
|
194
|
-
@click.option('--app-dir-path', default=
|
231
|
+
@click.option('--app-dir-path', default=current_working_dir, help='Path to application directory.')
|
232
|
+
@click.option('--context-dir-path', default=data_dir.context_dir_path, help='Path to Stoobly data directory.')
|
195
233
|
@click.option('--env', multiple=True, help='Specify an environment variable.')
|
196
234
|
@click.option('--force', is_flag=True, help='Overwrite maintained scaffolded workflow files.')
|
197
235
|
@click.option('--service', multiple=True, help='Specify the service(s) to create the workflow for.')
|
@@ -200,7 +238,7 @@ def workflow(ctx):
|
|
200
238
|
def create(**kwargs):
|
201
239
|
__validate_app_dir(kwargs['app_dir_path'])
|
202
240
|
|
203
|
-
app = App(kwargs['app_dir_path'], DOCKER_NAMESPACE)
|
241
|
+
app = App(kwargs['app_dir_path'], DOCKER_NAMESPACE, **kwargs)
|
204
242
|
|
205
243
|
for service_name in kwargs['service']:
|
206
244
|
config = { **kwargs }
|
@@ -212,19 +250,20 @@ def create(**kwargs):
|
|
212
250
|
|
213
251
|
workflow_dir_path = service.workflow_dir_path(kwargs['workflow_name'])
|
214
252
|
if kwargs['force'] or not os.path.exists(workflow_dir_path):
|
215
|
-
|
253
|
+
__workflow_create(app, **config)
|
216
254
|
else:
|
217
255
|
print(f"{workflow_dir_path} already exists, use option '--force' to continue")
|
218
256
|
|
219
257
|
@workflow.command(
|
220
258
|
help="Copy a workflow for service(s)",
|
221
259
|
)
|
222
|
-
@click.option('--app-dir-path', default=
|
260
|
+
@click.option('--app-dir-path', default=current_working_dir, help='Path to application directory.')
|
261
|
+
@click.option('--context-dir-path', default=data_dir.context_dir_path, help='Path to Stoobly data directory.')
|
223
262
|
@click.option('--service', multiple=True, help='Specify service(s) to add the workflow to.')
|
224
263
|
@click.argument('workflow_name')
|
225
264
|
@click.argument('destination_workflow_name')
|
226
265
|
def copy(**kwargs):
|
227
|
-
app = App(kwargs['app_dir_path'], DOCKER_NAMESPACE)
|
266
|
+
app = App(kwargs['app_dir_path'], DOCKER_NAMESPACE, **kwargs)
|
228
267
|
|
229
268
|
for service_name in kwargs['service']:
|
230
269
|
config = { **kwargs }
|
@@ -240,9 +279,10 @@ def copy(**kwargs):
|
|
240
279
|
command.copy(kwargs['destination_workflow_name'])
|
241
280
|
|
242
281
|
@workflow.command()
|
243
|
-
@click.option('--app-dir-path', default=
|
244
|
-
@click.option('--context-dir-path', default=
|
282
|
+
@click.option('--app-dir-path', default=current_working_dir, help='Path to application directory.')
|
283
|
+
@click.option('--context-dir-path', default=data_dir.context_dir_path, help='Path to Stoobly data directory.')
|
245
284
|
@click.option('--dry-run', default=False, is_flag=True)
|
285
|
+
@click.option('--extra-entrypoint-compose-path', help='Path to extra entrypoint compose file.')
|
246
286
|
@click.option('--log-level', default=INFO, type=click.Choice([DEBUG, INFO, WARNING, ERROR]), help='''
|
247
287
|
Log levels can be "debug", "info", "warning", or "error"
|
248
288
|
''')
|
@@ -258,15 +298,12 @@ def down(**kwargs):
|
|
258
298
|
os.environ[env_vars.LOG_LEVEL] = kwargs['log_level']
|
259
299
|
|
260
300
|
app = App(kwargs['app_dir_path'], DOCKER_NAMESPACE, **kwargs)
|
301
|
+
__validate_app(app)
|
261
302
|
|
262
303
|
# If namespace is set, default network to namespace
|
263
304
|
if kwargs['namespace'] and not kwargs['network']:
|
264
305
|
kwargs['network'] = kwargs['namespace']
|
265
306
|
|
266
|
-
if not app.exists:
|
267
|
-
print(f"Error: {app.dir_path} does not exist", file=sys.stderr)
|
268
|
-
sys.exit(1)
|
269
|
-
|
270
307
|
workflow = Workflow(kwargs['workflow_name'], app)
|
271
308
|
services = __get_services(workflow.services, service=kwargs['service'])
|
272
309
|
|
@@ -279,10 +316,22 @@ def down(**kwargs):
|
|
279
316
|
commands.append(command)
|
280
317
|
|
281
318
|
commands = sorted(commands, key=lambda command: command.service_config.priority)
|
282
|
-
for command in commands:
|
319
|
+
for index, command in enumerate(commands):
|
283
320
|
__print_header(f"SERVICE {command.service_name}")
|
284
321
|
|
285
|
-
|
322
|
+
extra_compose_path = None
|
323
|
+
|
324
|
+
# By default, the entrypoint service should be last
|
325
|
+
# However, this can change if the user has configured a service's priority to be higher
|
326
|
+
if index == len(commands) - 1:
|
327
|
+
extra_compose_path = kwargs['extra_entrypoint_compose_path']
|
328
|
+
|
329
|
+
exec_command = command.down(
|
330
|
+
extra_compose_path=extra_compose_path,
|
331
|
+
namespace=kwargs['namespace'],
|
332
|
+
rmi=kwargs['rmi'],
|
333
|
+
user_id=kwargs['user_id']
|
334
|
+
)
|
286
335
|
if not exec_command:
|
287
336
|
continue
|
288
337
|
|
@@ -309,7 +358,7 @@ def down(**kwargs):
|
|
309
358
|
print(remove_network_command)
|
310
359
|
|
311
360
|
@workflow.command()
|
312
|
-
@click.option('--app-dir-path', default=
|
361
|
+
@click.option('--app-dir-path', default=current_working_dir, help='Path to application directory.')
|
313
362
|
@click.option(
|
314
363
|
'--container', multiple=True, help=f"Select which containers to log. Defaults to '{WORKFLOW_CONTAINER_PROXY}'"
|
315
364
|
)
|
@@ -324,14 +373,11 @@ def down(**kwargs):
|
|
324
373
|
def logs(**kwargs):
|
325
374
|
os.environ[env_vars.LOG_LEVEL] = kwargs['log_level']
|
326
375
|
|
327
|
-
if len(kwargs['container']) == 0:
|
328
|
-
kwargs['container'] = [WORKFLOW_CONTAINER_PROXY]
|
329
|
-
|
330
376
|
app = App(kwargs['app_dir_path'], DOCKER_NAMESPACE)
|
377
|
+
__validate_app(app)
|
331
378
|
|
332
|
-
if
|
333
|
-
|
334
|
-
sys.exit(1)
|
379
|
+
if len(kwargs['container']) == 0:
|
380
|
+
kwargs['container'] = [WORKFLOW_CONTAINER_PROXY]
|
335
381
|
|
336
382
|
workflow = Workflow(kwargs['workflow_name'], app)
|
337
383
|
services = __get_services(workflow.services, service=kwargs['service'], without_core=True)
|
@@ -366,16 +412,16 @@ def logs(**kwargs):
|
|
366
412
|
|
367
413
|
if not kwargs['dry_run']:
|
368
414
|
exec_stream(shell_command)
|
369
|
-
|
415
|
+
|
370
416
|
@workflow.command()
|
371
|
-
@click.option('--app-dir-path', default=
|
417
|
+
@click.option('--app-dir-path', default=current_working_dir, help='Path to application directory.')
|
372
418
|
@click.option('--build', is_flag=True, help='Build images before starting containers.')
|
373
|
-
@click.option('--ca-certs-dir-path', default=
|
419
|
+
@click.option('--ca-certs-dir-path', default=data_dir.mitmproxy_conf_dir_path, help='Path to ca certs directory used to sign SSL certs. Defaults to ~/.mitmproxy')
|
374
420
|
@click.option('--certs-dir-path', help='Path to certs directory. Defaults to the certs dir of the context.')
|
375
|
-
@click.option('--context-dir-path', default=
|
421
|
+
@click.option('--context-dir-path', default=data_dir.context_dir_path, help='Path to Stoobly data directory.')
|
376
422
|
@click.option('--detached', is_flag=True, help='If set, will not run the highest priority service in the foreground.')
|
377
423
|
@click.option('--dry-run', default=False, is_flag=True, help='If set, prints commands.')
|
378
|
-
@click.option('--extra-compose-path', help='Path to extra compose
|
424
|
+
@click.option('--extra-entrypoint-compose-path', help='Path to extra entrypoint compose file.')
|
379
425
|
@click.option('--from-make', is_flag=True, help='Set if run from scaffolded Makefile.')
|
380
426
|
@click.option('--log-level', default=INFO, type=click.Choice([DEBUG, INFO, WARNING, ERROR]), help='''
|
381
427
|
Log levels can be "debug", "info", "warning", or "error"
|
@@ -393,15 +439,12 @@ def up(**kwargs):
|
|
393
439
|
os.environ[env_vars.LOG_LEVEL] = kwargs['log_level']
|
394
440
|
|
395
441
|
app = App(kwargs['app_dir_path'], DOCKER_NAMESPACE, **kwargs)
|
442
|
+
__validate_app(app)
|
396
443
|
|
397
444
|
# If namespace is set, default network to namespace
|
398
445
|
if kwargs['namespace'] and not kwargs['network']:
|
399
446
|
kwargs['network'] = kwargs['namespace']
|
400
447
|
|
401
|
-
if not app.exists:
|
402
|
-
print(f"Error: {app.dir_path} does not exist", file=sys.stderr)
|
403
|
-
sys.exit(1)
|
404
|
-
|
405
448
|
workflow = Workflow(kwargs['workflow_name'], app)
|
406
449
|
services = __get_services(workflow.services, service=kwargs['service'])
|
407
450
|
|
@@ -427,9 +470,9 @@ def up(**kwargs):
|
|
427
470
|
|
428
471
|
init_commands.append(command.create_network())
|
429
472
|
joined_command = ' && '.join(init_commands)
|
430
|
-
command.write_nameservers()
|
431
473
|
|
432
474
|
if not kwargs['dry_run']:
|
475
|
+
command.write_nameservers()
|
433
476
|
exec_stream(joined_command)
|
434
477
|
else:
|
435
478
|
print(joined_command)
|
@@ -438,11 +481,22 @@ def up(**kwargs):
|
|
438
481
|
for index, command in enumerate(commands):
|
439
482
|
__print_header(f"SERVICE {command.service_name}")
|
440
483
|
|
484
|
+
attached = False
|
485
|
+
extra_compose_path = None
|
486
|
+
|
441
487
|
# By default, the entrypoint service should be last
|
442
488
|
# However, this can change if the user has configured a service's priority to be higher
|
443
|
-
|
489
|
+
if index == len(commands) - 1:
|
490
|
+
attached = not kwargs['detached']
|
491
|
+
extra_compose_path = kwargs['extra_entrypoint_compose_path']
|
492
|
+
|
444
493
|
exec_command = command.up(
|
445
|
-
attached=attached,
|
494
|
+
attached=attached,
|
495
|
+
build=kwargs['build'],
|
496
|
+
extra_compose_path=extra_compose_path,
|
497
|
+
namespace=kwargs['namespace'],
|
498
|
+
pull=kwargs['pull'],
|
499
|
+
user_id=kwargs['user_id']
|
446
500
|
)
|
447
501
|
if not exec_command:
|
448
502
|
continue
|
@@ -455,10 +509,12 @@ def up(**kwargs):
|
|
455
509
|
@workflow.command(
|
456
510
|
help="Validate a scaffold workflow"
|
457
511
|
)
|
458
|
-
@click.option('--app-dir-path', default=
|
512
|
+
@click.option('--app-dir-path', default=current_working_dir, help='Path to validate the app scaffold.')
|
459
513
|
@click.argument('workflow_name')
|
460
514
|
def validate(**kwargs):
|
461
515
|
app = App(kwargs['app_dir_path'], DOCKER_NAMESPACE)
|
516
|
+
__validate_app(app)
|
517
|
+
|
462
518
|
workflow = Workflow(kwargs['workflow_name'], app)
|
463
519
|
|
464
520
|
config = { **kwargs }
|
@@ -481,28 +537,99 @@ def validate(**kwargs):
|
|
481
537
|
print(f"\nFatal Scaffold Validation Exception: {sve}", file=sys.stderr)
|
482
538
|
sys.exit(1)
|
483
539
|
|
540
|
+
@hostname.command(
|
541
|
+
help="Update the system hosts file for all scaffold service hostnames"
|
542
|
+
)
|
543
|
+
@click.option('--app-dir-path', default=current_working_dir, help='Path to application directory.')
|
544
|
+
@click.option('--service', multiple=True, help='Select specific services.')
|
545
|
+
@click.option('--workflow', multiple=True, help='Specify services by workflow(s).')
|
546
|
+
def install(**kwargs):
|
547
|
+
app = App(kwargs['app_dir_path'], DOCKER_NAMESPACE)
|
548
|
+
__validate_app(app)
|
549
|
+
|
550
|
+
services = __get_workflow_services(app, **kwargs)
|
551
|
+
|
552
|
+
hostnames = []
|
553
|
+
for service_name in services:
|
554
|
+
service = Service(service_name, app)
|
555
|
+
__validate_service_dir(service.dir_path)
|
556
|
+
|
557
|
+
service_config = ServiceConfig(service.dir_path)
|
558
|
+
if service_config.hostname:
|
559
|
+
hostnames.append(service_config.hostname)
|
560
|
+
|
561
|
+
__elevate_sudo()
|
562
|
+
|
563
|
+
try:
|
564
|
+
hosts_file_manager = HostsFileManager()
|
565
|
+
hosts_file_manager.install_hostnames(hostnames)
|
566
|
+
except PermissionError:
|
567
|
+
print("Permission denied. Please run this command with sudo.", file=sys.stderr)
|
568
|
+
|
569
|
+
@hostname.command(
|
570
|
+
help="Delete from the system hosts file all scaffold service hostnames"
|
571
|
+
)
|
572
|
+
@click.option('--app-dir-path', default=current_working_dir, help='Path to application directory.')
|
573
|
+
def uninstall(**kwargs):
|
574
|
+
app = App(kwargs['app_dir_path'], DOCKER_NAMESPACE)
|
575
|
+
__validate_app(app)
|
576
|
+
|
577
|
+
__elevate_sudo()
|
578
|
+
|
579
|
+
try:
|
580
|
+
hosts_file_manager = HostsFileManager()
|
581
|
+
hosts_file_manager.uninstall_hostnames()
|
582
|
+
except OSError as e:
|
583
|
+
if e.errno == errno.EACCES or e.errno == errno.EPERM:
|
584
|
+
print("Permission denied. Please run this command with sudo.", file=sys.stderr)
|
585
|
+
else:
|
586
|
+
print(f"An unexpected error occurred: {e}", file=sys.stderr)
|
587
|
+
|
484
588
|
scaffold.add_command(app)
|
485
589
|
scaffold.add_command(service)
|
486
590
|
scaffold.add_command(workflow)
|
591
|
+
scaffold.add_command(hostname)
|
487
592
|
|
488
|
-
def
|
489
|
-
|
490
|
-
missing_services = [service for service in kwargs['service'] if service not in services]
|
491
|
-
if missing_services:
|
492
|
-
Logger.instance(LOG_ID).warn(f"Service(s) {','.join(missing_services)} are not found")
|
593
|
+
def __elevate_sudo():
|
594
|
+
import subprocess
|
493
595
|
|
494
|
-
if
|
495
|
-
|
496
|
-
|
596
|
+
if os.geteuid() != 0:
|
597
|
+
subprocess.run(["sudo", sys.executable] + sys.argv)
|
598
|
+
sys.exit(0)
|
497
599
|
|
498
|
-
|
499
|
-
|
600
|
+
def __get_services(services: List[str], **kwargs) -> List[str]:
|
601
|
+
selected_services = list(kwargs['service'])
|
602
|
+
|
603
|
+
# If service is specified, run only those services
|
604
|
+
if selected_services:
|
605
|
+
missing_services = [service for service in selected_services if service not in services]
|
606
|
+
|
607
|
+
# Remove services that don't exist
|
608
|
+
if missing_services:
|
609
|
+
Logger.instance(LOG_ID).warn(f"Service(s) {','.join(missing_services)} are not found")
|
610
|
+
selected_services = list(set(selected_services) - set(missing_services))
|
611
|
+
|
612
|
+
services = selected_services
|
613
|
+
|
614
|
+
services += CORE_SERVICES
|
615
|
+
|
616
|
+
services_index = {}
|
617
|
+
for service in services:
|
618
|
+
if kwargs.get('without_core') and service in CORE_SERVICES:
|
619
|
+
continue
|
620
|
+
services_index[service] = True
|
621
|
+
|
622
|
+
return services_index.keys()
|
623
|
+
|
624
|
+
def __get_workflow_services(app: App, **kwargs):
|
625
|
+
selected_services = []
|
626
|
+
if not kwargs['workflow']:
|
627
|
+
selected_services += __get_services(app.services, service=kwargs['service'], without_core=True)
|
500
628
|
else:
|
501
|
-
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
return list(set(services))
|
629
|
+
for workflow_name in kwargs['workflow']:
|
630
|
+
workflow = Workflow(workflow_name, app)
|
631
|
+
selected_services += __get_services(workflow.services, service=kwargs['service'], without_core=True)
|
632
|
+
return set(selected_services)
|
506
633
|
|
507
634
|
def __print_header(text: str):
|
508
635
|
Logger.instance(LOG_ID).info(f"{bcolors.OKBLUE}{text}{bcolors.ENDC}")
|
@@ -517,6 +644,11 @@ def __scaffold_delete(app, **kwargs):
|
|
517
644
|
|
518
645
|
command.delete()
|
519
646
|
|
647
|
+
def __validate_app(app: App):
|
648
|
+
if not app.valid:
|
649
|
+
print(f"Error: {app.dir_path} is not a valid scaffold app", file=sys.stderr)
|
650
|
+
sys.exit(1)
|
651
|
+
|
520
652
|
def __validate_app_dir(app_dir_path):
|
521
653
|
if not os.path.exists(app_dir_path):
|
522
654
|
print(f"Error: {app_dir_path} does not exist", file=sys.stderr)
|
@@ -527,13 +659,12 @@ def __validate_service_dir(service_dir_path):
|
|
527
659
|
print(f"Error: {service_dir_path} does not exist, please scaffold this service", file=sys.stderr)
|
528
660
|
sys.exit(1)
|
529
661
|
|
530
|
-
def
|
662
|
+
def __workflow_create(app, **kwargs):
|
531
663
|
command = WorkflowCreateCommand(app, **kwargs)
|
532
664
|
|
533
665
|
service_config = command.service_config
|
534
666
|
workflow_decorators = get_workflow_decorators(kwargs['template'], service_config)
|
535
667
|
command.build(
|
536
|
-
headless=kwargs['headless'],
|
537
668
|
template=kwargs['template'],
|
538
669
|
workflow_decorators=workflow_decorators
|
539
670
|
)
|
@@ -100,7 +100,7 @@ def prune(**kwargs):
|
|
100
100
|
log.prune(kwargs['dry_run'])
|
101
101
|
|
102
102
|
@snapshot.command(
|
103
|
-
help="Update snapshot.",
|
103
|
+
help="Update a snapshot.",
|
104
104
|
)
|
105
105
|
@click.option('--format', type=click.Choice(FORMATS), help='Format output.')
|
106
106
|
@click.option('--select', multiple=True, help='Select column(s) to display.')
|
@@ -71,6 +71,7 @@ def handle_request_mock_generic(context: MockContext, **options: MockOptions):
|
|
71
71
|
context.with_response(res)
|
72
72
|
|
73
73
|
if handle_success:
|
74
|
+
# TODO: rewrite response, see #332
|
74
75
|
res = handle_success(context) or res
|
75
76
|
elif policy == mock_policy.FOUND:
|
76
77
|
res = eval_request_with_retry(context, eval_request, **options)
|
@@ -86,6 +87,7 @@ def handle_request_mock_generic(context: MockContext, **options: MockOptions):
|
|
86
87
|
pass
|
87
88
|
else:
|
88
89
|
if handle_success:
|
90
|
+
# TODO: rewrite response, see #332
|
89
91
|
res = handle_success(context) or res
|
90
92
|
else:
|
91
93
|
return bad_request(
|
@@ -24,6 +24,8 @@ def handle_request_replay(replay_context: ReplayContext):
|
|
24
24
|
def handle_response_replay(replay_context: ReplayContext):
|
25
25
|
__replay_hook(lifecycle_hooks.AFTER_REPLAY, replay_context)
|
26
26
|
|
27
|
+
# TODO: rewrite response, see #332
|
28
|
+
|
27
29
|
def __replay_request(replay_context: ReplayContext):
|
28
30
|
"""
|
29
31
|
Before replaying a request, see if the request needs to be rewritten
|
@@ -0,0 +1,19 @@
|
|
1
|
+
import pdb
|
2
|
+
|
3
|
+
from mitmproxy.http import Response as MitmproxyResponse
|
4
|
+
from typing import Union
|
5
|
+
|
6
|
+
from ..replay.body_parser_service import decode_response, encode_response
|
7
|
+
|
8
|
+
class MitmproxyResponseBodyFacade:
|
9
|
+
def __init__(self, response: MitmproxyResponse):
|
10
|
+
self.__response = response
|
11
|
+
|
12
|
+
def get(self, content_type: Union[bytes, str]):
|
13
|
+
return decode_response(self.__response.content, content_type)
|
14
|
+
|
15
|
+
def set(self, content, content_type: Union[bytes, str]):
|
16
|
+
"""
|
17
|
+
Adjusting Content-Length header should be done by MitmproxyResponse
|
18
|
+
"""
|
19
|
+
self.__response.content = encode_response(content, content_type).encode()
|