stoobly-agent 1.2.3__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 +7 -6
- stoobly_agent/app/cli/helpers/print_service.py +17 -0
- stoobly_agent/app/cli/scaffold/app.py +16 -34
- stoobly_agent/app/cli/scaffold/app_command.py +4 -7
- stoobly_agent/app/cli/scaffold/app_config.py +15 -2
- stoobly_agent/app/cli/scaffold/app_create_command.py +18 -2
- stoobly_agent/app/cli/scaffold/command.py +1 -1
- stoobly_agent/app/cli/scaffold/constants.py +9 -5
- stoobly_agent/app/cli/scaffold/docker/app_builder.py +3 -7
- stoobly_agent/app/cli/scaffold/docker/constants.py +0 -1
- stoobly_agent/app/cli/scaffold/docker/service/builder.py +12 -11
- stoobly_agent/app/cli/scaffold/docker/workflow/builder.py +14 -31
- stoobly_agent/app/cli/scaffold/docker/workflow/mock_decorator.py +6 -2
- stoobly_agent/app/cli/scaffold/docker/workflow/reverse_proxy_decorator.py +6 -2
- stoobly_agent/app/cli/scaffold/hosts_file_manager.py +112 -0
- stoobly_agent/app/cli/scaffold/service.py +1 -2
- stoobly_agent/app/cli/scaffold/service_command.py +1 -1
- stoobly_agent/app/cli/scaffold/service_config.py +10 -14
- stoobly_agent/app/cli/scaffold/service_workflow_validate_command.py +9 -11
- stoobly_agent/app/cli/scaffold/templates/app/.Dockerfile.context +2 -4
- stoobly_agent/app/cli/scaffold/templates/app/.Makefile +108 -68
- stoobly_agent/app/cli/scaffold/templates/app/.docker-compose.base.yml +8 -13
- stoobly_agent/app/cli/scaffold/templates/app/Makefile +1 -1
- stoobly_agent/app/cli/scaffold/templates/app/build/.docker-compose.base.yml +8 -4
- stoobly_agent/app/cli/scaffold/templates/app/build/mock/.docker-compose.mock.yml +2 -6
- stoobly_agent/app/cli/scaffold/templates/app/build/mock/bin/.configure +3 -0
- stoobly_agent/app/cli/scaffold/templates/app/build/mock/bin/.init +3 -0
- stoobly_agent/app/cli/scaffold/templates/app/build/record/.docker-compose.record.yml +2 -6
- stoobly_agent/app/cli/scaffold/templates/app/build/record/bin/.configure +3 -0
- stoobly_agent/app/cli/scaffold/templates/app/build/record/bin/.init +3 -0
- stoobly_agent/app/cli/scaffold/templates/app/build/test/.docker-compose.test.yml +2 -6
- stoobly_agent/app/cli/scaffold/templates/app/build/test/bin/.configure +3 -0
- stoobly_agent/app/cli/scaffold/templates/app/build/test/bin/.init +3 -0
- stoobly_agent/app/cli/scaffold/templates/app/entrypoint/.docker-compose.base.yml +2 -0
- stoobly_agent/app/cli/scaffold/templates/app/entrypoint/mock/.docker-compose.mock.yml +2 -8
- stoobly_agent/app/cli/scaffold/templates/app/entrypoint/record/.docker-compose.record.yml +2 -8
- stoobly_agent/app/cli/scaffold/templates/app/entrypoint/test/.docker-compose.test.yml +2 -8
- stoobly_agent/app/cli/scaffold/templates/app/stoobly-ui/exec/.docker-compose.exec.yml +2 -3
- stoobly_agent/app/cli/scaffold/templates/app/stoobly-ui/exec/bin/.logs +1 -0
- stoobly_agent/app/cli/scaffold/templates/app/stoobly-ui/exec/bin/.services +9 -0
- stoobly_agent/app/cli/scaffold/templates/app/stoobly-ui/mock/.docker-compose.mock.yml +1 -2
- stoobly_agent/app/cli/scaffold/templates/app/stoobly-ui/record/.docker-compose.record.yml +1 -2
- stoobly_agent/app/cli/scaffold/templates/workflow/mock/bin/.configure +3 -0
- stoobly_agent/app/cli/scaffold/templates/workflow/mock/bin/.init +7 -1
- stoobly_agent/app/cli/scaffold/templates/workflow/record/bin/.configure +3 -0
- stoobly_agent/app/cli/scaffold/templates/workflow/record/bin/.init +7 -1
- stoobly_agent/app/cli/scaffold/templates/workflow/test/bin/.configure +3 -0
- stoobly_agent/app/cli/scaffold/templates/workflow/test/bin/.init +7 -1
- stoobly_agent/app/cli/scaffold/validate_command.py +2 -2
- stoobly_agent/app/cli/scaffold/workflow.py +5 -4
- stoobly_agent/app/cli/scaffold/workflow_command.py +3 -3
- stoobly_agent/app/cli/scaffold/workflow_create_command.py +0 -1
- stoobly_agent/app/cli/scaffold/workflow_run_command.py +78 -45
- stoobly_agent/app/cli/scaffold_cli.py +246 -109
- stoobly_agent/app/cli/snapshot_cli.py +7 -3
- stoobly_agent/app/models/adapters/joined_request_adapter.py +6 -0
- stoobly_agent/app/models/factories/resource/local_db/helpers/scenario_snapshot.py +3 -1
- stoobly_agent/app/models/helpers/apply.py +34 -17
- stoobly_agent/app/models/helpers/create_request_params_service.py +4 -0
- 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/proxy/replay/body_parser_service.py +11 -3
- 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/config/data_dir.py +2 -1
- stoobly_agent/config/schema.yml +2 -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/cli_invoker.py +1 -2
- stoobly_agent/test/app/cli/scaffold/{hosts_file_reader_test.py → hosts_file_manager_test.py} +20 -20
- stoobly_agent/test/app/cli/snapshot/snapshot_apply_test.py +162 -1
- stoobly_agent/test/app/models/schemas/.stoobly/db/VERSION +1 -1
- stoobly_agent/test/mock_data/scaffold/docker-compose-assets-service.yml +1 -3
- {stoobly_agent-1.2.3.dist-info → stoobly_agent-1.4.0.dist-info}/METADATA +1 -1
- {stoobly_agent-1.2.3.dist-info → stoobly_agent-1.4.0.dist-info}/RECORD +94 -93
- stoobly_agent/app/cli/scaffold/hosts_file_reader.py +0 -65
- stoobly_agent/app/cli/scaffold/templates/app/.Dockerfile.proxy +0 -34
- 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.2.3.dist-info → stoobly_agent-1.4.0.dist-info}/LICENSE +0 -0
- {stoobly_agent-1.2.3.dist-info → stoobly_agent-1.4.0.dist-info}/WHEEL +0 -0
- {stoobly_agent-1.2.3.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,42 +64,53 @@ 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.')
|
88
|
+
@click.option('--network', help='App default network name. Defaults to app name.')
|
65
89
|
@click.argument('app_name')
|
66
90
|
def create(**kwargs):
|
67
91
|
__validate_app_dir(kwargs['app_dir_path'])
|
68
92
|
|
69
93
|
app = App(kwargs['app_dir_path'], DOCKER_NAMESPACE)
|
70
94
|
|
71
|
-
if kwargs['force'] or not os.path.exists(app.
|
72
|
-
|
95
|
+
if kwargs['force'] or not os.path.exists(app.scaffold_namespace_path):
|
96
|
+
if not kwargs['network']:
|
97
|
+
kwargs['network'] = kwargs['app_name']
|
98
|
+
|
99
|
+
AppCreateCommand(app, **kwargs).build()
|
73
100
|
else:
|
74
101
|
print(f"{kwargs['app_dir_path']} already exists, use option '--force' to continue ")
|
75
102
|
|
76
103
|
@app.command(
|
77
104
|
help="Scaffold app service certs"
|
78
105
|
)
|
79
|
-
@click.option('--app-dir-path', default=
|
80
|
-
@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')
|
81
108
|
@click.option('--certs-dir-path', help='Path to certs directory. Defaults to the certs dir of the context.')
|
82
|
-
@click.option('--context-dir-path', default=
|
109
|
+
@click.option('--context-dir-path', default=data_dir.context_dir_path, help='Path to Stoobly data directory.')
|
83
110
|
@click.option('--service', multiple=True, help='Select which services to run. Defaults to all.')
|
84
111
|
def mkcert(**kwargs):
|
85
|
-
app = App(kwargs['app_dir_path'], DOCKER_NAMESPACE,
|
86
|
-
|
87
|
-
if kwargs['certs_dir_path']:
|
88
|
-
app.certs_dir_path = kwargs['certs_dir_path']
|
89
|
-
|
90
|
-
if kwargs['context_dir_path']:
|
91
|
-
app.context_dir_path = kwargs['context_dir_path']
|
92
|
-
|
93
|
-
if not app.exists:
|
94
|
-
print(f"Error: {app.dir_path} does not exist", file=sys.stderr)
|
95
|
-
sys.exit(1)
|
112
|
+
app = App(kwargs['app_dir_path'], DOCKER_NAMESPACE, **kwargs)
|
113
|
+
__validate_app(app)
|
96
114
|
|
97
115
|
services = __get_services(app.services, service=kwargs['service'])
|
98
116
|
|
@@ -116,7 +134,7 @@ def mkcert(**kwargs):
|
|
116
134
|
@service.command(
|
117
135
|
help="Scaffold a service",
|
118
136
|
)
|
119
|
-
@click.option('--app-dir-path', default=
|
137
|
+
@click.option('--app-dir-path', default=current_working_dir, help='Path to application directory.')
|
120
138
|
@click.option('--detached', is_flag=True)
|
121
139
|
@click.option('--env', multiple=True, help='Specify an environment variable.')
|
122
140
|
@click.option('--force', is_flag=True, help='Overwrite maintained scaffolded service files.')
|
@@ -144,9 +162,34 @@ def create(**kwargs):
|
|
144
162
|
print(f"{service.dir_path} already exists, use option '--force' to continue")
|
145
163
|
|
146
164
|
@service.command(
|
147
|
-
help="
|
165
|
+
help="List services",
|
166
|
+
name="list"
|
148
167
|
)
|
149
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.')
|
150
193
|
@click.argument('service_name')
|
151
194
|
def delete(**kwargs):
|
152
195
|
__validate_app_dir(kwargs['app_dir_path'])
|
@@ -164,7 +207,7 @@ def delete(**kwargs):
|
|
164
207
|
@service.command(
|
165
208
|
help="Update a service config"
|
166
209
|
)
|
167
|
-
@click.option('--app-dir-path', default=
|
210
|
+
@click.option('--app-dir-path', default=current_working_dir, help='Path to application directory.')
|
168
211
|
@click.option('--priority', help='Determines the service run order.')
|
169
212
|
@click.argument('service_name')
|
170
213
|
def update(**kwargs):
|
@@ -182,18 +225,11 @@ def update(**kwargs):
|
|
182
225
|
|
183
226
|
service_config.write()
|
184
227
|
|
185
|
-
@click.group(
|
186
|
-
epilog="Run 'stoobly-agent request response COMMAND --help' for more information on a command.",
|
187
|
-
help="Manage service scaffold"
|
188
|
-
)
|
189
|
-
@click.pass_context
|
190
|
-
def workflow(ctx):
|
191
|
-
pass
|
192
|
-
|
193
228
|
@workflow.command(
|
194
229
|
help="Create workflow for service(s)"
|
195
230
|
)
|
196
|
-
@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.')
|
197
233
|
@click.option('--env', multiple=True, help='Specify an environment variable.')
|
198
234
|
@click.option('--force', is_flag=True, help='Overwrite maintained scaffolded workflow files.')
|
199
235
|
@click.option('--service', multiple=True, help='Specify the service(s) to create the workflow for.')
|
@@ -202,7 +238,7 @@ def workflow(ctx):
|
|
202
238
|
def create(**kwargs):
|
203
239
|
__validate_app_dir(kwargs['app_dir_path'])
|
204
240
|
|
205
|
-
app = App(kwargs['app_dir_path'], DOCKER_NAMESPACE)
|
241
|
+
app = App(kwargs['app_dir_path'], DOCKER_NAMESPACE, **kwargs)
|
206
242
|
|
207
243
|
for service_name in kwargs['service']:
|
208
244
|
config = { **kwargs }
|
@@ -214,19 +250,20 @@ def create(**kwargs):
|
|
214
250
|
|
215
251
|
workflow_dir_path = service.workflow_dir_path(kwargs['workflow_name'])
|
216
252
|
if kwargs['force'] or not os.path.exists(workflow_dir_path):
|
217
|
-
|
253
|
+
__workflow_create(app, **config)
|
218
254
|
else:
|
219
255
|
print(f"{workflow_dir_path} already exists, use option '--force' to continue")
|
220
256
|
|
221
257
|
@workflow.command(
|
222
258
|
help="Copy a workflow for service(s)",
|
223
259
|
)
|
224
|
-
@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.')
|
225
262
|
@click.option('--service', multiple=True, help='Specify service(s) to add the workflow to.')
|
226
263
|
@click.argument('workflow_name')
|
227
264
|
@click.argument('destination_workflow_name')
|
228
265
|
def copy(**kwargs):
|
229
|
-
app = App(kwargs['app_dir_path'], DOCKER_NAMESPACE)
|
266
|
+
app = App(kwargs['app_dir_path'], DOCKER_NAMESPACE, **kwargs)
|
230
267
|
|
231
268
|
for service_name in kwargs['service']:
|
232
269
|
config = { **kwargs }
|
@@ -242,31 +279,30 @@ def copy(**kwargs):
|
|
242
279
|
command.copy(kwargs['destination_workflow_name'])
|
243
280
|
|
244
281
|
@workflow.command()
|
245
|
-
@click.option('--app-dir-path', default=
|
246
|
-
@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.')
|
247
284
|
@click.option('--dry-run', default=False, is_flag=True)
|
285
|
+
@click.option('--extra-entrypoint-compose-path', help='Path to extra entrypoint compose file.')
|
248
286
|
@click.option('--log-level', default=INFO, type=click.Choice([DEBUG, INFO, WARNING, ERROR]), help='''
|
249
287
|
Log levels can be "debug", "info", "warning", or "error"
|
250
288
|
''')
|
251
289
|
@click.option('--namespace', help='Workflow namespace.')
|
252
|
-
@click.option('--network', help='Workflow network
|
290
|
+
@click.option('--network', help='Workflow network name.')
|
253
291
|
@click.option('--rmi', is_flag=True, help='Remove images used by containers.')
|
254
292
|
@click.option('--service', multiple=True, help='Select which services to log. Defaults to all.')
|
293
|
+
@click.option('--user-id', default=os.getuid(), help='OS user ID of the owner of context dir path.')
|
255
294
|
@click.argument('workflow_name')
|
256
295
|
def down(**kwargs):
|
257
296
|
cwd = os.getcwd()
|
258
297
|
|
259
|
-
|
260
|
-
os.environ[env_vars.LOG_LEVEL] = kwargs['log_level']
|
298
|
+
os.environ[env_vars.LOG_LEVEL] = kwargs['log_level']
|
261
299
|
|
262
|
-
app = App(kwargs['app_dir_path'], DOCKER_NAMESPACE,
|
300
|
+
app = App(kwargs['app_dir_path'], DOCKER_NAMESPACE, **kwargs)
|
301
|
+
__validate_app(app)
|
263
302
|
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
if not app.exists:
|
268
|
-
print(f"Error: {app.dir_path} does not exist", file=sys.stderr)
|
269
|
-
sys.exit(1)
|
303
|
+
# If namespace is set, default network to namespace
|
304
|
+
if kwargs['namespace'] and not kwargs['network']:
|
305
|
+
kwargs['network'] = kwargs['namespace']
|
270
306
|
|
271
307
|
workflow = Workflow(kwargs['workflow_name'], app)
|
272
308
|
services = __get_services(workflow.services, service=kwargs['service'])
|
@@ -280,10 +316,22 @@ def down(**kwargs):
|
|
280
316
|
commands.append(command)
|
281
317
|
|
282
318
|
commands = sorted(commands, key=lambda command: command.service_config.priority)
|
283
|
-
for command in commands:
|
319
|
+
for index, command in enumerate(commands):
|
284
320
|
__print_header(f"SERVICE {command.service_name}")
|
285
321
|
|
286
|
-
|
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
|
+
)
|
287
335
|
if not exec_command:
|
288
336
|
continue
|
289
337
|
|
@@ -295,34 +343,41 @@ def down(**kwargs):
|
|
295
343
|
# After services are stopped, their network needs to be removed
|
296
344
|
if len(commands) > 0:
|
297
345
|
command: WorkflowRunCommand = commands[0]
|
298
|
-
remove_network_command = command.remove_network()
|
299
346
|
|
300
|
-
if
|
301
|
-
command.
|
347
|
+
if kwargs['rmi']:
|
348
|
+
remove_image_command = command.remove_image(kwargs['user_id'])
|
349
|
+
if not kwargs['dry_run']:
|
350
|
+
exec_stream(remove_image_command)
|
351
|
+
else:
|
352
|
+
print(remove_image_command)
|
302
353
|
|
354
|
+
remove_network_command = command.remove_network()
|
355
|
+
if not kwargs['dry_run']:
|
303
356
|
exec_stream(remove_network_command)
|
304
357
|
else:
|
305
358
|
print(remove_network_command)
|
306
359
|
|
307
360
|
@workflow.command()
|
308
|
-
@click.option('--app-dir-path', default=
|
361
|
+
@click.option('--app-dir-path', default=current_working_dir, help='Path to application directory.')
|
309
362
|
@click.option(
|
310
363
|
'--container', multiple=True, help=f"Select which containers to log. Defaults to '{WORKFLOW_CONTAINER_PROXY}'"
|
311
364
|
)
|
312
365
|
@click.option('--dry-run', default=False, is_flag=True, help='If set, prints commands.')
|
313
366
|
@click.option('--follow', is_flag=True, help='Follow last container log output.')
|
367
|
+
@click.option('--log-level', default=INFO, type=click.Choice([DEBUG, INFO, WARNING, ERROR]), help='''
|
368
|
+
Log levels can be "debug", "info", "warning", or "error"
|
369
|
+
''')
|
314
370
|
@click.option('--namespace', help='Workflow namespace.')
|
315
371
|
@click.option('--service', multiple=True, help='Select which services to log. Defaults to all.')
|
316
372
|
@click.argument('workflow_name')
|
317
373
|
def logs(**kwargs):
|
318
|
-
|
319
|
-
kwargs['container'] = [WORKFLOW_CONTAINER_PROXY]
|
374
|
+
os.environ[env_vars.LOG_LEVEL] = kwargs['log_level']
|
320
375
|
|
321
376
|
app = App(kwargs['app_dir_path'], DOCKER_NAMESPACE)
|
377
|
+
__validate_app(app)
|
322
378
|
|
323
|
-
if
|
324
|
-
|
325
|
-
sys.exit(1)
|
379
|
+
if len(kwargs['container']) == 0:
|
380
|
+
kwargs['container'] = [WORKFLOW_CONTAINER_PROXY]
|
326
381
|
|
327
382
|
workflow = Workflow(kwargs['workflow_name'], app)
|
328
383
|
services = __get_services(workflow.services, service=kwargs['service'], without_core=True)
|
@@ -357,47 +412,38 @@ def logs(**kwargs):
|
|
357
412
|
|
358
413
|
if not kwargs['dry_run']:
|
359
414
|
exec_stream(shell_command)
|
360
|
-
|
415
|
+
|
361
416
|
@workflow.command()
|
362
|
-
@click.option('--app-dir-path', default=
|
363
|
-
@click.option('--
|
417
|
+
@click.option('--app-dir-path', default=current_working_dir, help='Path to application directory.')
|
418
|
+
@click.option('--build', is_flag=True, help='Build images before starting containers.')
|
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')
|
364
420
|
@click.option('--certs-dir-path', help='Path to certs directory. Defaults to the certs dir of the context.')
|
365
|
-
@click.option('--context-dir-path', default=
|
421
|
+
@click.option('--context-dir-path', default=data_dir.context_dir_path, help='Path to Stoobly data directory.')
|
366
422
|
@click.option('--detached', is_flag=True, help='If set, will not run the highest priority service in the foreground.')
|
367
423
|
@click.option('--dry-run', default=False, is_flag=True, help='If set, prints commands.')
|
368
|
-
@click.option('--extra-compose-path', help='Path to extra compose
|
424
|
+
@click.option('--extra-entrypoint-compose-path', help='Path to extra entrypoint compose file.')
|
425
|
+
@click.option('--from-make', is_flag=True, help='Set if run from scaffolded Makefile.')
|
369
426
|
@click.option('--log-level', default=INFO, type=click.Choice([DEBUG, INFO, WARNING, ERROR]), help='''
|
370
427
|
Log levels can be "debug", "info", "warning", or "error"
|
371
428
|
''')
|
372
429
|
@click.option('--namespace', help='Workflow namespace.')
|
373
|
-
@click.option('--network', help='Workflow network
|
374
|
-
@click.option('--
|
430
|
+
@click.option('--network', help='Workflow network name.')
|
431
|
+
@click.option('--pull', is_flag=True, help='Pull image before running.')
|
375
432
|
@click.option('--service', multiple=True, help='Select which services to run. Defaults to all.')
|
433
|
+
@click.option('--user-id', default=os.getuid(), help='OS user ID of the owner of context dir path.')
|
376
434
|
@click.option('--verbose', is_flag=True)
|
377
435
|
@click.argument('workflow_name')
|
378
436
|
def up(**kwargs):
|
379
437
|
cwd = os.getcwd()
|
380
438
|
|
381
|
-
|
382
|
-
DataDir.instance().certs_dir_path
|
439
|
+
os.environ[env_vars.LOG_LEVEL] = kwargs['log_level']
|
383
440
|
|
384
|
-
|
385
|
-
|
441
|
+
app = App(kwargs['app_dir_path'], DOCKER_NAMESPACE, **kwargs)
|
442
|
+
__validate_app(app)
|
386
443
|
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
)
|
391
|
-
|
392
|
-
if kwargs['certs_dir_path']:
|
393
|
-
app.certs_dir_path = kwargs['certs_dir_path']
|
394
|
-
|
395
|
-
if kwargs['context_dir_path']:
|
396
|
-
app.context_dir_path = kwargs['context_dir_path']
|
397
|
-
|
398
|
-
if not app.exists:
|
399
|
-
print(f"Error: {app.dir_path} does not exist", file=sys.stderr)
|
400
|
-
sys.exit(1)
|
444
|
+
# If namespace is set, default network to namespace
|
445
|
+
if kwargs['namespace'] and not kwargs['network']:
|
446
|
+
kwargs['network'] = kwargs['namespace']
|
401
447
|
|
402
448
|
workflow = Workflow(kwargs['workflow_name'], app)
|
403
449
|
services = __get_services(workflow.services, service=kwargs['service'])
|
@@ -413,27 +459,44 @@ def up(**kwargs):
|
|
413
459
|
command.current_working_dir = cwd
|
414
460
|
commands.append(command)
|
415
461
|
|
416
|
-
# Before services can be started, their network needs to be created
|
462
|
+
# Before services can be started, their image and network needs to be created
|
417
463
|
if len(commands) > 0:
|
418
464
|
command: WorkflowRunCommand = commands[0]
|
419
|
-
|
465
|
+
|
466
|
+
init_commands = []
|
467
|
+
if not kwargs['from_make']:
|
468
|
+
create_image_command = command.create_image(user_id=kwargs['user_id'], verbose=kwargs['verbose'])
|
469
|
+
init_commands.append(create_image_command)
|
470
|
+
|
471
|
+
init_commands.append(command.create_network())
|
472
|
+
joined_command = ' && '.join(init_commands)
|
420
473
|
|
421
474
|
if not kwargs['dry_run']:
|
422
475
|
command.write_nameservers()
|
423
|
-
|
424
|
-
exec_stream(create_network_command)
|
476
|
+
exec_stream(joined_command)
|
425
477
|
else:
|
426
|
-
print(
|
478
|
+
print(joined_command)
|
427
479
|
|
428
480
|
commands = sorted(commands, key=lambda command: command.service_config.priority)
|
429
481
|
for index, command in enumerate(commands):
|
430
482
|
__print_header(f"SERVICE {command.service_name}")
|
431
483
|
|
484
|
+
attached = False
|
485
|
+
extra_compose_path = None
|
486
|
+
|
432
487
|
# By default, the entrypoint service should be last
|
433
488
|
# However, this can change if the user has configured a service's priority to be higher
|
434
|
-
|
489
|
+
if index == len(commands) - 1:
|
490
|
+
attached = not kwargs['detached']
|
491
|
+
extra_compose_path = kwargs['extra_entrypoint_compose_path']
|
492
|
+
|
435
493
|
exec_command = command.up(
|
436
|
-
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']
|
437
500
|
)
|
438
501
|
if not exec_command:
|
439
502
|
continue
|
@@ -446,10 +509,12 @@ def up(**kwargs):
|
|
446
509
|
@workflow.command(
|
447
510
|
help="Validate a scaffold workflow"
|
448
511
|
)
|
449
|
-
@click.option('--app-dir-path', default=
|
512
|
+
@click.option('--app-dir-path', default=current_working_dir, help='Path to validate the app scaffold.')
|
450
513
|
@click.argument('workflow_name')
|
451
514
|
def validate(**kwargs):
|
452
515
|
app = App(kwargs['app_dir_path'], DOCKER_NAMESPACE)
|
516
|
+
__validate_app(app)
|
517
|
+
|
453
518
|
workflow = Workflow(kwargs['workflow_name'], app)
|
454
519
|
|
455
520
|
config = { **kwargs }
|
@@ -472,35 +537,103 @@ def validate(**kwargs):
|
|
472
537
|
print(f"\nFatal Scaffold Validation Exception: {sve}", file=sys.stderr)
|
473
538
|
sys.exit(1)
|
474
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
|
+
|
475
588
|
scaffold.add_command(app)
|
476
589
|
scaffold.add_command(service)
|
477
590
|
scaffold.add_command(workflow)
|
591
|
+
scaffold.add_command(hostname)
|
592
|
+
|
593
|
+
def __elevate_sudo():
|
594
|
+
import subprocess
|
595
|
+
|
596
|
+
if os.geteuid() != 0:
|
597
|
+
subprocess.run(["sudo", sys.executable] + sys.argv)
|
598
|
+
sys.exit(0)
|
599
|
+
|
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
|
478
613
|
|
479
|
-
|
480
|
-
# Log services that don't exist
|
481
|
-
missing_services = [service for service in kwargs['service'] if service not in services]
|
482
|
-
if missing_services:
|
483
|
-
Logger.instance(LOG_ID).warn(f"Service(s) {','.join(missing_services)} are not found")
|
614
|
+
services += CORE_SERVICES
|
484
615
|
|
485
|
-
|
486
|
-
|
487
|
-
|
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()
|
488
623
|
|
489
|
-
|
490
|
-
|
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)
|
491
628
|
else:
|
492
|
-
|
493
|
-
|
494
|
-
|
495
|
-
|
496
|
-
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)
|
497
633
|
|
498
634
|
def __print_header(text: str):
|
499
635
|
Logger.instance(LOG_ID).info(f"{bcolors.OKBLUE}{text}{bcolors.ENDC}")
|
500
636
|
|
501
|
-
def __app_build(app, **kwargs):
|
502
|
-
AppCreateCommand(app, **kwargs).build()
|
503
|
-
|
504
637
|
def __scaffold_build(app, **kwargs):
|
505
638
|
command = ServiceCreateCommand(app, **kwargs)
|
506
639
|
|
@@ -511,6 +644,11 @@ def __scaffold_delete(app, **kwargs):
|
|
511
644
|
|
512
645
|
command.delete()
|
513
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
|
+
|
514
652
|
def __validate_app_dir(app_dir_path):
|
515
653
|
if not os.path.exists(app_dir_path):
|
516
654
|
print(f"Error: {app_dir_path} does not exist", file=sys.stderr)
|
@@ -521,13 +659,12 @@ def __validate_service_dir(service_dir_path):
|
|
521
659
|
print(f"Error: {service_dir_path} does not exist, please scaffold this service", file=sys.stderr)
|
522
660
|
sys.exit(1)
|
523
661
|
|
524
|
-
def
|
662
|
+
def __workflow_create(app, **kwargs):
|
525
663
|
command = WorkflowCreateCommand(app, **kwargs)
|
526
664
|
|
527
665
|
service_config = command.service_config
|
528
666
|
workflow_decorators = get_workflow_decorators(kwargs['template'], service_config)
|
529
667
|
command.build(
|
530
|
-
headless=kwargs['headless'],
|
531
668
|
template=kwargs['template'],
|
532
669
|
workflow_decorators=workflow_decorators
|
533
670
|
)
|
@@ -36,11 +36,15 @@ def snapshot(ctx):
|
|
36
36
|
@click.argument('uuid', required=False)
|
37
37
|
def apply(**kwargs):
|
38
38
|
apply = Apply(force=kwargs['force']).with_logger(print)
|
39
|
+
completed = True
|
39
40
|
|
40
41
|
if kwargs.get('uuid'):
|
41
|
-
apply.single(kwargs['uuid'])
|
42
|
+
completed = apply.single(kwargs['uuid'])
|
42
43
|
else:
|
43
|
-
apply.all()
|
44
|
+
completed = apply.all()
|
45
|
+
|
46
|
+
if not completed:
|
47
|
+
sys.exit(1)
|
44
48
|
|
45
49
|
@snapshot.command(
|
46
50
|
help="Copy snapshots to a different data directory."
|
@@ -96,7 +100,7 @@ def prune(**kwargs):
|
|
96
100
|
log.prune(kwargs['dry_run'])
|
97
101
|
|
98
102
|
@snapshot.command(
|
99
|
-
help="Update snapshot.",
|
103
|
+
help="Update a snapshot.",
|
100
104
|
)
|
101
105
|
@click.option('--format', type=click.Choice(FORMATS), help='Format output.')
|
102
106
|
@click.option('--select', multiple=True, help='Select column(s) to display.')
|
@@ -18,6 +18,12 @@ class JoinedRequestAdapter():
|
|
18
18
|
payloads_delimitter = payloads_delimitter.encode()
|
19
19
|
|
20
20
|
self.__split_joined_request_string = joined_request_string.split(payloads_delimitter)
|
21
|
+
if len(self.__split_joined_request_string) != 2:
|
22
|
+
self.__split_joined_request_string = joined_request_string.split(payloads_delimitter.replace(b"\n", b"\r\n"))
|
23
|
+
|
24
|
+
if len(self.__split_joined_request_string) != 2:
|
25
|
+
raise ValueError(f"Could not split by {payloads_delimitter}")
|
26
|
+
|
21
27
|
self.__raw_request_string = None
|
22
28
|
self.__raw_response_string = None
|
23
29
|
|