stoobly-agent 0.34.13__py3-none-any.whl → 1.0.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/cli/__init__.py +1 -0
- stoobly_agent/app/cli/helpers/openapi_endpoint_adapter.py +9 -7
- stoobly_agent/app/cli/helpers/shell.py +28 -0
- stoobly_agent/app/cli/main_group.py +1 -1
- stoobly_agent/app/cli/scaffold/__init__.py +0 -0
- stoobly_agent/app/cli/scaffold/app.py +106 -0
- stoobly_agent/app/cli/scaffold/app_command.py +82 -0
- stoobly_agent/app/cli/scaffold/app_config.py +32 -0
- stoobly_agent/app/cli/scaffold/app_create_command.py +24 -0
- stoobly_agent/app/cli/scaffold/command.py +15 -0
- stoobly_agent/app/cli/scaffold/config.py +35 -0
- stoobly_agent/app/cli/scaffold/constants.py +35 -0
- stoobly_agent/app/cli/scaffold/docker/__init__.py +0 -0
- stoobly_agent/app/cli/scaffold/docker/app_builder.py +26 -0
- stoobly_agent/app/cli/scaffold/docker/builder.py +117 -0
- stoobly_agent/app/cli/scaffold/docker/constants.py +7 -0
- stoobly_agent/app/cli/scaffold/docker/service/__init__.py +0 -0
- stoobly_agent/app/cli/scaffold/docker/service/build_decorator.py +37 -0
- stoobly_agent/app/cli/scaffold/docker/service/builder.py +117 -0
- stoobly_agent/app/cli/scaffold/docker/service/set_gateway_ports.py +47 -0
- stoobly_agent/app/cli/scaffold/docker/service/types.py +4 -0
- stoobly_agent/app/cli/scaffold/docker/workflow/__init__.py +0 -0
- stoobly_agent/app/cli/scaffold/docker/workflow/build_decorator.py +28 -0
- stoobly_agent/app/cli/scaffold/docker/workflow/builder.py +259 -0
- stoobly_agent/app/cli/scaffold/docker/workflow/decorators_factory.py +17 -0
- stoobly_agent/app/cli/scaffold/docker/workflow/mock_decorator.py +40 -0
- stoobly_agent/app/cli/scaffold/docker/workflow/reverse_proxy_decorator.py +51 -0
- stoobly_agent/app/cli/scaffold/env.py +49 -0
- stoobly_agent/app/cli/scaffold/service.py +25 -0
- stoobly_agent/app/cli/scaffold/service_command.py +50 -0
- stoobly_agent/app/cli/scaffold/service_config.py +207 -0
- stoobly_agent/app/cli/scaffold/service_create_command.py +77 -0
- stoobly_agent/app/cli/scaffold/service_workflow.py +18 -0
- stoobly_agent/app/cli/scaffold/templates/__init__.py +0 -0
- stoobly_agent/app/cli/scaffold/templates/app/.Dockerfile.context +8 -0
- stoobly_agent/app/cli/scaffold/templates/app/.Dockerfile.proxy +35 -0
- stoobly_agent/app/cli/scaffold/templates/app/.Makefile +118 -0
- stoobly_agent/app/cli/scaffold/templates/app/.docker-compose.base.yml +15 -0
- stoobly_agent/app/cli/scaffold/templates/app/Makefile +3 -0
- stoobly_agent/app/cli/scaffold/templates/app/build/.config.yml +1 -0
- stoobly_agent/app/cli/scaffold/templates/app/build/.docker-compose.base.yml +11 -0
- stoobly_agent/app/cli/scaffold/templates/app/build/mock/.docker-compose.mock.yml +22 -0
- stoobly_agent/app/cli/scaffold/templates/app/build/mock/bin/.configure +5 -0
- stoobly_agent/app/cli/scaffold/templates/app/build/mock/bin/.init +5 -0
- stoobly_agent/app/cli/scaffold/templates/app/build/mock/bin/configure +1 -0
- stoobly_agent/app/cli/scaffold/templates/app/build/mock/bin/init +1 -0
- stoobly_agent/app/cli/scaffold/templates/app/build/record/.docker-compose.record.yml +22 -0
- stoobly_agent/app/cli/scaffold/templates/app/build/record/bin/.configure +5 -0
- stoobly_agent/app/cli/scaffold/templates/app/build/record/bin/.init +5 -0
- stoobly_agent/app/cli/scaffold/templates/app/build/record/bin/configure +1 -0
- stoobly_agent/app/cli/scaffold/templates/app/build/record/bin/init +1 -0
- stoobly_agent/app/cli/scaffold/templates/app/build/test/.docker-compose.test.yml +22 -0
- stoobly_agent/app/cli/scaffold/templates/app/build/test/bin/.configure +5 -0
- stoobly_agent/app/cli/scaffold/templates/app/build/test/bin/.init +5 -0
- stoobly_agent/app/cli/scaffold/templates/app/build/test/bin/configure +1 -0
- stoobly_agent/app/cli/scaffold/templates/app/build/test/bin/init +1 -0
- stoobly_agent/app/cli/scaffold/templates/app/entrypoint/.config.yml +1 -0
- stoobly_agent/app/cli/scaffold/templates/app/entrypoint/.docker-compose.base.yml +17 -0
- stoobly_agent/app/cli/scaffold/templates/app/entrypoint/mock/.docker-compose.mock.yml +31 -0
- stoobly_agent/app/cli/scaffold/templates/app/entrypoint/mock/bin/.configure +10 -0
- stoobly_agent/app/cli/scaffold/templates/app/entrypoint/mock/bin/.init +5 -0
- stoobly_agent/app/cli/scaffold/templates/app/entrypoint/mock/bin/configure +1 -0
- stoobly_agent/app/cli/scaffold/templates/app/entrypoint/mock/bin/init +4 -0
- stoobly_agent/app/cli/scaffold/templates/app/entrypoint/mock/docker-compose.yml +1 -0
- stoobly_agent/app/cli/scaffold/templates/app/entrypoint/record/.docker-compose.record.yml +31 -0
- stoobly_agent/app/cli/scaffold/templates/app/entrypoint/record/bin/.configure +10 -0
- stoobly_agent/app/cli/scaffold/templates/app/entrypoint/record/bin/.init +5 -0
- stoobly_agent/app/cli/scaffold/templates/app/entrypoint/record/bin/configure +1 -0
- stoobly_agent/app/cli/scaffold/templates/app/entrypoint/record/bin/init +4 -0
- stoobly_agent/app/cli/scaffold/templates/app/entrypoint/record/docker-compose.yml +1 -0
- stoobly_agent/app/cli/scaffold/templates/app/entrypoint/test/.docker-compose.test.yml +31 -0
- stoobly_agent/app/cli/scaffold/templates/app/entrypoint/test/bin/.configure +10 -0
- stoobly_agent/app/cli/scaffold/templates/app/entrypoint/test/bin/.init +3 -0
- stoobly_agent/app/cli/scaffold/templates/app/entrypoint/test/bin/configure +1 -0
- stoobly_agent/app/cli/scaffold/templates/app/entrypoint/test/bin/init +4 -0
- stoobly_agent/app/cli/scaffold/templates/app/entrypoint/test/docker-compose.yml +1 -0
- stoobly_agent/app/cli/scaffold/templates/app/gateway/.config.yml +1 -0
- stoobly_agent/app/cli/scaffold/templates/app/gateway/.docker-compose.base.yml +11 -0
- stoobly_agent/app/cli/scaffold/templates/app/gateway/mock/.docker-compose.mock.yml +13 -0
- stoobly_agent/app/cli/scaffold/templates/app/gateway/record/.docker-compose.record.yml +13 -0
- stoobly_agent/app/cli/scaffold/templates/app/stoobly-ui/.config.yml +1 -0
- stoobly_agent/app/cli/scaffold/templates/app/stoobly-ui/.docker-compose.base.yml +5 -0
- stoobly_agent/app/cli/scaffold/templates/app/stoobly-ui/exec/.docker-compose.exec.yml +12 -0
- stoobly_agent/app/cli/scaffold/templates/app/stoobly-ui/exec/bin/.create +11 -0
- stoobly_agent/app/cli/scaffold/templates/app/stoobly-ui/exec/bin/.delete +12 -0
- stoobly_agent/app/cli/scaffold/templates/app/stoobly-ui/exec/bin/.disable +3 -0
- stoobly_agent/app/cli/scaffold/templates/app/stoobly-ui/exec/bin/.enable +10 -0
- stoobly_agent/app/cli/scaffold/templates/app/stoobly-ui/exec/bin/.reset +12 -0
- stoobly_agent/app/cli/scaffold/templates/app/stoobly-ui/exec/bin/.run +11 -0
- stoobly_agent/app/cli/scaffold/templates/app/stoobly-ui/exec/bin/.snapshot +12 -0
- stoobly_agent/app/cli/scaffold/templates/app/stoobly-ui/exec/bin/.stop +11 -0
- stoobly_agent/app/cli/scaffold/templates/app/stoobly-ui/mock/.docker-compose.mock.yml +12 -0
- stoobly_agent/app/cli/scaffold/templates/app/stoobly-ui/record/.docker-compose.record.yml +13 -0
- stoobly_agent/app/cli/scaffold/templates/constants.py +63 -0
- stoobly_agent/app/cli/scaffold/templates/factory.py +46 -0
- stoobly_agent/app/cli/scaffold/templates/workflow/mock/bin/.configure +3 -0
- stoobly_agent/app/cli/scaffold/templates/workflow/mock/bin/.init +3 -0
- stoobly_agent/app/cli/scaffold/templates/workflow/mock/bin/configure +18 -0
- stoobly_agent/app/cli/scaffold/templates/workflow/mock/bin/init +4 -0
- stoobly_agent/app/cli/scaffold/templates/workflow/mock/fixtures/.keep +0 -0
- stoobly_agent/app/cli/scaffold/templates/workflow/mock/fixtures.yml +5 -0
- stoobly_agent/app/cli/scaffold/templates/workflow/mock/lifecycle_hooks.py +12 -0
- stoobly_agent/app/cli/scaffold/templates/workflow/record/bin/.configure +3 -0
- stoobly_agent/app/cli/scaffold/templates/workflow/record/bin/.init +3 -0
- stoobly_agent/app/cli/scaffold/templates/workflow/record/bin/configure +29 -0
- stoobly_agent/app/cli/scaffold/templates/workflow/record/bin/init +4 -0
- stoobly_agent/app/cli/scaffold/templates/workflow/record/lifecycle_hooks.py +12 -0
- stoobly_agent/app/cli/scaffold/templates/workflow/test/bin/.configure +3 -0
- stoobly_agent/app/cli/scaffold/templates/workflow/test/bin/.init +3 -0
- stoobly_agent/app/cli/scaffold/templates/workflow/test/bin/configure +18 -0
- stoobly_agent/app/cli/scaffold/templates/workflow/test/bin/init +4 -0
- stoobly_agent/app/cli/scaffold/templates/workflow/test/fixtures/.keep +0 -0
- stoobly_agent/app/cli/scaffold/templates/workflow/test/fixtures.yml +5 -0
- stoobly_agent/app/cli/scaffold/templates/workflow/test/lifecycle_hooks.py +12 -0
- stoobly_agent/app/cli/scaffold/workflow.py +49 -0
- stoobly_agent/app/cli/scaffold/workflow_command.py +137 -0
- stoobly_agent/app/cli/scaffold/workflow_copy_command.py +45 -0
- stoobly_agent/app/cli/scaffold/workflow_create_command.py +94 -0
- stoobly_agent/app/cli/scaffold/workflow_log_command.py +21 -0
- stoobly_agent/app/cli/scaffold/workflow_run_command.py +134 -0
- stoobly_agent/app/cli/scaffold_cli.py +392 -0
- stoobly_agent/cli.py +3 -2
- stoobly_agent/config/data_dir.py +40 -14
- stoobly_agent/lib/logger.py +3 -2
- stoobly_agent/test/app/models/schemas/.stoobly/db/VERSION +1 -1
- stoobly_agent/test/config/data_dir_test.py +4 -4
- {stoobly_agent-0.34.13.dist-info → stoobly_agent-1.0.0.dist-info}/METADATA +8 -7
- {stoobly_agent-0.34.13.dist-info → stoobly_agent-1.0.0.dist-info}/RECORD +132 -14
- {stoobly_agent-0.34.13.dist-info → stoobly_agent-1.0.0.dist-info}/WHEEL +1 -1
- {stoobly_agent-0.34.13.dist-info → stoobly_agent-1.0.0.dist-info}/LICENSE +0 -0
- {stoobly_agent-0.34.13.dist-info → stoobly_agent-1.0.0.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,392 @@
|
|
1
|
+
import click
|
2
|
+
import os
|
3
|
+
import pdb
|
4
|
+
import sys
|
5
|
+
|
6
|
+
from typing import List
|
7
|
+
|
8
|
+
from stoobly_agent.app.cli.helpers.shell import exec_stream
|
9
|
+
from stoobly_agent.app.cli.scaffold.app import App
|
10
|
+
from stoobly_agent.app.cli.scaffold.app_create_command import AppCreateCommand
|
11
|
+
from stoobly_agent.app.cli.scaffold.constants import (
|
12
|
+
DOCKER_NAMESPACE, WORKFLOW_CUSTOM_FILTER, WORKFLOW_MOCK_TYPE, WORKFLOW_RECORD_TYPE, WORKFLOW_TEST_TYPE
|
13
|
+
)
|
14
|
+
from stoobly_agent.app.cli.scaffold.docker.service.set_gateway_ports import set_gateway_ports
|
15
|
+
from stoobly_agent.app.cli.scaffold.docker.workflow.decorators_factory import get_workflow_decorators
|
16
|
+
from stoobly_agent.app.cli.scaffold.service import Service
|
17
|
+
from stoobly_agent.app.cli.scaffold.service_config import ServiceConfig
|
18
|
+
from stoobly_agent.app.cli.scaffold.service_create_command import ServiceCreateCommand
|
19
|
+
from stoobly_agent.app.cli.scaffold.templates.constants import CORE_SERVICES
|
20
|
+
from stoobly_agent.app.cli.scaffold.workflow import Workflow
|
21
|
+
from stoobly_agent.app.cli.scaffold.workflow_create_command import WorkflowCreateCommand
|
22
|
+
from stoobly_agent.app.cli.scaffold.workflow_copy_command import WorkflowCopyCommand
|
23
|
+
from stoobly_agent.app.cli.scaffold.workflow_log_command import WorkflowLogCommand
|
24
|
+
from stoobly_agent.app.cli.scaffold.workflow_run_command import WorkflowRunCommand
|
25
|
+
from stoobly_agent.config.constants import env_vars
|
26
|
+
from stoobly_agent.config.data_dir import DataDir
|
27
|
+
from stoobly_agent.lib.logger import bcolors, DEBUG, ERROR, INFO, Logger, WARNING
|
28
|
+
|
29
|
+
LOG_ID = 'Scaffold'
|
30
|
+
|
31
|
+
@click.group(
|
32
|
+
epilog="Run 'stoobly-agent project COMMAND --help' for more information on a command.",
|
33
|
+
help="Manage scaffold"
|
34
|
+
)
|
35
|
+
@click.pass_context
|
36
|
+
def scaffold(ctx):
|
37
|
+
pass
|
38
|
+
|
39
|
+
@click.group(
|
40
|
+
epilog="Run 'stoobly-agent request response COMMAND --help' for more information on a command.",
|
41
|
+
help="Manage app scaffold"
|
42
|
+
)
|
43
|
+
@click.pass_context
|
44
|
+
def app(ctx):
|
45
|
+
pass
|
46
|
+
|
47
|
+
@click.group(
|
48
|
+
epilog="Run 'stoobly-agent request response COMMAND --help' for more information on a command.",
|
49
|
+
help="Manage service scaffold"
|
50
|
+
)
|
51
|
+
@click.pass_context
|
52
|
+
def service(ctx):
|
53
|
+
pass
|
54
|
+
|
55
|
+
@app.command(
|
56
|
+
help="Scaffold application"
|
57
|
+
)
|
58
|
+
@click.option('--app-dir-path', default=os.getcwd(), help='Path to create the app scaffold.')
|
59
|
+
@click.option('--force', is_flag=True, help='Overwrite maintained scaffolded app files.')
|
60
|
+
@click.argument('app_name')
|
61
|
+
def create(**kwargs):
|
62
|
+
__validate_app_dir(kwargs['app_dir_path'])
|
63
|
+
|
64
|
+
app = App(kwargs['app_dir_path'], DOCKER_NAMESPACE)
|
65
|
+
|
66
|
+
if kwargs['force'] or not os.path.exists(app.namespace_path):
|
67
|
+
__app_build(app, **kwargs)
|
68
|
+
else:
|
69
|
+
print(f"{kwargs['app_dir_path']} already exists, use option '--force' to continue ")
|
70
|
+
|
71
|
+
def service_create_options(f):
|
72
|
+
def wrapper(*args, **kwargs):
|
73
|
+
return f(*args, **kwargs)
|
74
|
+
return wrapper
|
75
|
+
|
76
|
+
@service.command(
|
77
|
+
help="Scaffold a service",
|
78
|
+
)
|
79
|
+
@click.option('--app-dir-path', default=os.getcwd(), help='Path to application directory.')
|
80
|
+
@click.option('--detached', is_flag=True)
|
81
|
+
@click.option('--env', multiple=True, help='Specify an environment variable.')
|
82
|
+
@click.option('--force', is_flag=True, help='Overwrite maintained scaffolded service files.')
|
83
|
+
@click.option('--hostname')
|
84
|
+
@click.option('--port')
|
85
|
+
@click.option('--priority', default='5.0', help='Determines the service run order.')
|
86
|
+
@click.option('--proxy-mode', help='''
|
87
|
+
Proxy mode can be "regular", "transparent", "socks5",
|
88
|
+
"reverse:SPEC", or "upstream:SPEC". For reverse and
|
89
|
+
upstream proxy modes, SPEC is host specification in
|
90
|
+
the form of "http[s]://host[:port]".
|
91
|
+
''')
|
92
|
+
@click.option('--scheme', type=click.Choice(['http', 'https']))
|
93
|
+
@click.option('--workflow', multiple=True, type=click.Choice([WORKFLOW_MOCK_TYPE, WORKFLOW_RECORD_TYPE, WORKFLOW_TEST_TYPE]), help='Include pre-defined workflows.')
|
94
|
+
@click.argument('service_name')
|
95
|
+
def create(**kwargs):
|
96
|
+
__validate_app_dir(kwargs['app_dir_path'])
|
97
|
+
|
98
|
+
app = App(kwargs['app_dir_path'], DOCKER_NAMESPACE)
|
99
|
+
|
100
|
+
service = Service(kwargs['service_name'], app)
|
101
|
+
if kwargs['force'] or not os.path.exists(service.dir_path):
|
102
|
+
__scaffold_build(app, **kwargs)
|
103
|
+
else:
|
104
|
+
print(f"{service.dir_path} already exists, use option '--force' to continue")
|
105
|
+
|
106
|
+
@service.command(
|
107
|
+
help="Update a service config"
|
108
|
+
)
|
109
|
+
@click.option('--app-dir-path', default=os.getcwd(), help='Path to application directory.')
|
110
|
+
@click.option('--priority', help='Determines the service run order.')
|
111
|
+
@click.argument('service_name')
|
112
|
+
def update(**kwargs):
|
113
|
+
__validate_app_dir(kwargs['app_dir_path'])
|
114
|
+
|
115
|
+
app = App(kwargs['app_dir_path'], DOCKER_NAMESPACE)
|
116
|
+
service = Service(kwargs['service_name'], app)
|
117
|
+
|
118
|
+
__validate_service_dir(service.dir_path)
|
119
|
+
|
120
|
+
service_config = ServiceConfig(service.dir_path)
|
121
|
+
|
122
|
+
if kwargs['priority']:
|
123
|
+
service_config.priority = kwargs['priority']
|
124
|
+
|
125
|
+
service_config.write()
|
126
|
+
|
127
|
+
@click.group(
|
128
|
+
epilog="Run 'stoobly-agent request response COMMAND --help' for more information on a command.",
|
129
|
+
help="Manage service scaffold"
|
130
|
+
)
|
131
|
+
@click.pass_context
|
132
|
+
def workflow(ctx):
|
133
|
+
pass
|
134
|
+
|
135
|
+
@workflow.command(
|
136
|
+
help="Create workflow for service(s)"
|
137
|
+
)
|
138
|
+
@click.option('--app-dir-path', default=os.getcwd(), help='Path to application directory.')
|
139
|
+
@click.option('--env', multiple=True, help='Specify an environment variable.')
|
140
|
+
@click.option('--force', is_flag=True, help='Overwrite maintained scaffolded workflow files.')
|
141
|
+
@click.option('--service', multiple=True, help='Specify the service(s) to create the workflow for.')
|
142
|
+
@click.option('--template', type=click.Choice([WORKFLOW_MOCK_TYPE, WORKFLOW_RECORD_TYPE, WORKFLOW_TEST_TYPE]), help='Select which workflow to use as a template.')
|
143
|
+
@click.argument('workflow_name')
|
144
|
+
def create(**kwargs):
|
145
|
+
__validate_app_dir(kwargs['app_dir_path'])
|
146
|
+
|
147
|
+
app = App(kwargs['app_dir_path'], DOCKER_NAMESPACE)
|
148
|
+
|
149
|
+
for service_name in kwargs['service']:
|
150
|
+
config = { **kwargs }
|
151
|
+
del config['service']
|
152
|
+
config['service_name'] = service_name
|
153
|
+
|
154
|
+
service = Service(service_name, app)
|
155
|
+
__validate_service_dir(service.dir_path)
|
156
|
+
|
157
|
+
workflow_dir_path = service.workflow_dir_path(kwargs['workflow_name'])
|
158
|
+
if kwargs['force'] or not os.path.exists(workflow_dir_path):
|
159
|
+
__workflow_build(app, **config)
|
160
|
+
else:
|
161
|
+
print(f"{workflow_dir_path} already exists, use option '--force' to continue")
|
162
|
+
|
163
|
+
@workflow.command(
|
164
|
+
help="Copy a workflow for service(s)",
|
165
|
+
)
|
166
|
+
@click.option('--app-dir-path', default=os.getcwd(), help='Path to application directory.')
|
167
|
+
@click.option('--service', multiple=True, help='Specify service(s) to add the workflow to.')
|
168
|
+
@click.argument('workflow_name')
|
169
|
+
@click.argument('destination_workflow_name')
|
170
|
+
def copy(**kwargs):
|
171
|
+
app = App(kwargs['app_dir_path'], DOCKER_NAMESPACE)
|
172
|
+
|
173
|
+
for service_name in kwargs['service']:
|
174
|
+
config = { **kwargs }
|
175
|
+
del config['service']
|
176
|
+
config['service_name'] = service_name
|
177
|
+
|
178
|
+
command = WorkflowCopyCommand(app, **config)
|
179
|
+
|
180
|
+
if not command.app_dir_exists:
|
181
|
+
print(f"Error: {command.app_dir_path} does not exist", file=sys.stderr)
|
182
|
+
sys.exit(1)
|
183
|
+
|
184
|
+
command.copy(kwargs['destination_workflow_name'])
|
185
|
+
|
186
|
+
@workflow.command()
|
187
|
+
@click.option('--app-dir-path', default=os.getcwd(), help='Path to application directory.')
|
188
|
+
@click.option('--context-dir-path', default=DataDir.instance().context_dir_path, help='Path to Stoobly data directory.')
|
189
|
+
@click.option('--dry-run', default=False, is_flag=True)
|
190
|
+
|
191
|
+
@click.option('--filter', multiple=True, type=click.Choice([WORKFLOW_CUSTOM_FILTER]), help='Select which service groups to run. Defaults to all.')
|
192
|
+
@click.option('--log-level', default=INFO, type=click.Choice([DEBUG, INFO, WARNING, ERROR]), help='''
|
193
|
+
Log levels can be "debug", "info", "warning", or "error"
|
194
|
+
''')
|
195
|
+
@click.option('--service', multiple=True, help='Select which services to log. Defaults to all.')
|
196
|
+
@click.argument('workflow_name')
|
197
|
+
def stop(**kwargs):
|
198
|
+
cwd = os.getcwd()
|
199
|
+
|
200
|
+
if not os.getenv(env_vars.LOG_LEVEL):
|
201
|
+
os.environ[env_vars.LOG_LEVEL] = kwargs['log_level']
|
202
|
+
|
203
|
+
app = App(kwargs['app_dir_path'], DOCKER_NAMESPACE, skip_validate_path=True)
|
204
|
+
|
205
|
+
if kwargs['context_dir_path']:
|
206
|
+
app.context_dir_path = kwargs['context_dir_path']
|
207
|
+
|
208
|
+
if not app.exists:
|
209
|
+
print(f"Error: {app.dir_path} does not exist", file=sys.stderr)
|
210
|
+
sys.exit(1)
|
211
|
+
|
212
|
+
workflow = Workflow(kwargs['workflow_name'], app)
|
213
|
+
services = __get_services(workflow.services, filter=kwargs['filter'], service=kwargs['service'])
|
214
|
+
|
215
|
+
commands: List[WorkflowRunCommand] = []
|
216
|
+
for service in services:
|
217
|
+
config = { **kwargs }
|
218
|
+
config['service_name'] = service
|
219
|
+
command = WorkflowRunCommand(app, **config)
|
220
|
+
command.current_working_dir = cwd
|
221
|
+
commands.append(command)
|
222
|
+
|
223
|
+
commands = sorted(commands, key=lambda command: command.service_config.priority)
|
224
|
+
|
225
|
+
for command in commands:
|
226
|
+
__print_header(f"SERVICE {command.service_name}")
|
227
|
+
|
228
|
+
exec_command = command.down()
|
229
|
+
if not exec_command:
|
230
|
+
continue
|
231
|
+
|
232
|
+
if not kwargs['dry_run']:
|
233
|
+
exec_stream(exec_command)
|
234
|
+
else:
|
235
|
+
print(exec_command)
|
236
|
+
|
237
|
+
@workflow.command()
|
238
|
+
@click.option('--app-dir-path', default=os.getcwd(), help='Path to application directory.')
|
239
|
+
@click.option('--dry-run', default=False, is_flag=True)
|
240
|
+
@click.option('--filter', multiple=True, type=click.Choice([WORKFLOW_CUSTOM_FILTER]), help='Select which service groups to run. Defaults to all.')
|
241
|
+
@click.option('--service', multiple=True, help='Select which services to log. Defaults to all.')
|
242
|
+
@click.argument('workflow_name')
|
243
|
+
def logs(**kwargs):
|
244
|
+
app = App(kwargs['app_dir_path'], DOCKER_NAMESPACE)
|
245
|
+
|
246
|
+
if not app.exists:
|
247
|
+
print(f"Error: {app.dir_path} does not exist", file=sys.stderr)
|
248
|
+
sys.exit(1)
|
249
|
+
|
250
|
+
workflow = Workflow(kwargs['workflow_name'], app)
|
251
|
+
services = __get_services(workflow.services, filter=kwargs['filter'], service=kwargs['service'])
|
252
|
+
|
253
|
+
commands: List[WorkflowLogCommand] = []
|
254
|
+
for service in services:
|
255
|
+
if len(kwargs['service']) > 0 and service not in kwargs['service']:
|
256
|
+
continue
|
257
|
+
|
258
|
+
config = { **kwargs }
|
259
|
+
config['service_name'] = service
|
260
|
+
command = WorkflowLogCommand(app, **config)
|
261
|
+
commands.append(command)
|
262
|
+
|
263
|
+
commands = sorted(commands, key=lambda command: command.service_config.priority)
|
264
|
+
|
265
|
+
for command in commands:
|
266
|
+
__print_header(f"SERVICE {command.service_name}")
|
267
|
+
|
268
|
+
for shell_command in command.all():
|
269
|
+
print(shell_command)
|
270
|
+
|
271
|
+
if not kwargs['dry_run']:
|
272
|
+
exec_stream(shell_command)
|
273
|
+
|
274
|
+
@workflow.command()
|
275
|
+
@click.option('--app-dir-path', default=os.getcwd(), help='Path to application directory.')
|
276
|
+
@click.option('--certs-dir-path', default=DataDir.instance().certs_dir_path, help='Path to certs directory.')
|
277
|
+
@click.option('--context-dir-path', default=DataDir.instance().path, help='Path to Stoobly data directory.')
|
278
|
+
@click.option('--filter', multiple=True, type=click.Choice([WORKFLOW_CUSTOM_FILTER]), help='Select which service groups to run. Defaults to all.')
|
279
|
+
@click.option('--dry-run', default=False, is_flag=True, help='If set, prints commands.')
|
280
|
+
@click.option('--extra-compose-path', help='Path to extra compose configuration files.')
|
281
|
+
@click.option('--log-level', default=INFO, type=click.Choice([DEBUG, INFO, WARNING, ERROR]), help='''
|
282
|
+
Log levels can be "debug", "info", "warning", or "error"
|
283
|
+
''')
|
284
|
+
@click.option('--network', help='Name of network namespace.')
|
285
|
+
@click.option('--service', multiple=True, help='Select which services to run. Defaults to all.')
|
286
|
+
@click.argument('workflow_name')
|
287
|
+
def run(**kwargs):
|
288
|
+
cwd = os.getcwd()
|
289
|
+
|
290
|
+
if not os.getenv(env_vars.LOG_LEVEL):
|
291
|
+
os.environ[env_vars.LOG_LEVEL] = kwargs['log_level']
|
292
|
+
|
293
|
+
app = App(kwargs['app_dir_path'], DOCKER_NAMESPACE, skip_validate_path=kwargs['dry_run'])
|
294
|
+
if kwargs['certs_dir_path']:
|
295
|
+
app.certs_dir_path = kwargs['certs_dir_path']
|
296
|
+
|
297
|
+
if kwargs['context_dir_path']:
|
298
|
+
app.context_dir_path = kwargs['context_dir_path']
|
299
|
+
|
300
|
+
if not app.exists:
|
301
|
+
print(f"Error: {app.dir_path} does not exist", file=sys.stderr)
|
302
|
+
sys.exit(1)
|
303
|
+
|
304
|
+
workflow = Workflow(kwargs['workflow_name'], app)
|
305
|
+
services = __get_services(workflow.services, filter=kwargs['filter'], service=kwargs['service'])
|
306
|
+
|
307
|
+
# Gateway ports are dynamically set depending on the workflow run
|
308
|
+
set_gateway_ports(workflow.service_paths_from_services(services))
|
309
|
+
|
310
|
+
commands: List[WorkflowRunCommand] = []
|
311
|
+
for service in services:
|
312
|
+
config = { **kwargs }
|
313
|
+
config['service_name'] = service
|
314
|
+
command = WorkflowRunCommand(app, **config)
|
315
|
+
command.current_working_dir = cwd
|
316
|
+
commands.append(command)
|
317
|
+
|
318
|
+
# Before services can be started, their network needs to be created
|
319
|
+
if len(commands) > 0:
|
320
|
+
create_network_command = commands[0].create_network()
|
321
|
+
if not kwargs['dry_run']:
|
322
|
+
exec_stream(create_network_command)
|
323
|
+
else:
|
324
|
+
print(create_network_command)
|
325
|
+
|
326
|
+
commands = sorted(commands, key=lambda command: command.service_config.priority)
|
327
|
+
for command in commands:
|
328
|
+
__print_header(f"SERVICE {command.service_name}")
|
329
|
+
|
330
|
+
exec_command = command.up()
|
331
|
+
if not exec_command:
|
332
|
+
continue
|
333
|
+
|
334
|
+
if not kwargs['dry_run']:
|
335
|
+
exec_stream(exec_command)
|
336
|
+
else:
|
337
|
+
print(exec_command)
|
338
|
+
|
339
|
+
scaffold.add_command(app)
|
340
|
+
scaffold.add_command(service)
|
341
|
+
scaffold.add_command(workflow)
|
342
|
+
|
343
|
+
def __get_services(services: List[str], **kwargs):
|
344
|
+
# Log services that don't exist
|
345
|
+
missing_services = [service for service in kwargs['service'] if service not in services]
|
346
|
+
if missing_services:
|
347
|
+
Logger.instance(WARNING).warn(f"Service(s) {','.join(missing_services)} are not found")
|
348
|
+
|
349
|
+
if kwargs['service']:
|
350
|
+
# If service is specified, run only those services
|
351
|
+
services = list(kwargs['service'])
|
352
|
+
|
353
|
+
if WORKFLOW_CUSTOM_FILTER not in kwargs['filter']:
|
354
|
+
services += CORE_SERVICES
|
355
|
+
else:
|
356
|
+
if WORKFLOW_CUSTOM_FILTER in kwargs['filter']:
|
357
|
+
# If this filter is set, then the user does not want to run core services
|
358
|
+
services = list(filter(lambda service: service not in CORE_SERVICES, services))
|
359
|
+
|
360
|
+
return list(set(services))
|
361
|
+
|
362
|
+
def __print_header(text: str):
|
363
|
+
Logger.instance(LOG_ID).info(f"{bcolors.OKBLUE}{text}{bcolors.ENDC}")
|
364
|
+
|
365
|
+
def __app_build(app, **kwargs):
|
366
|
+
AppCreateCommand(app, **kwargs).build()
|
367
|
+
|
368
|
+
def __scaffold_build(app, **kwargs):
|
369
|
+
command = ServiceCreateCommand(app, **kwargs)
|
370
|
+
|
371
|
+
command.build()
|
372
|
+
|
373
|
+
def __validate_app_dir(app_dir_path):
|
374
|
+
if not os.path.exists(app_dir_path):
|
375
|
+
print(f"Error: {app_dir_path} does not exist", file=sys.stderr)
|
376
|
+
sys.exit(1)
|
377
|
+
|
378
|
+
def __validate_service_dir(service_dir_path):
|
379
|
+
if not os.path.exists(service_dir_path):
|
380
|
+
print(f"Error: {service_dir_path} does not exist, please scaffold this service", file=sys.stderr)
|
381
|
+
sys.exit(1)
|
382
|
+
|
383
|
+
def __workflow_build(app, **kwargs):
|
384
|
+
command = WorkflowCreateCommand(app, **kwargs)
|
385
|
+
|
386
|
+
service_config = command.service_config
|
387
|
+
workflow_decorators = get_workflow_decorators(kwargs['template'], service_config)
|
388
|
+
command.build(
|
389
|
+
headless=kwargs['headless'],
|
390
|
+
template=kwargs['template'],
|
391
|
+
workflow_decorators=workflow_decorators
|
392
|
+
)
|
stoobly_agent/cli.py
CHANGED
@@ -16,14 +16,14 @@ from stoobly_agent.config.data_dir import DataDir
|
|
16
16
|
from stoobly_agent.lib.utils.conditional_decorator import ConditionalDecorator
|
17
17
|
|
18
18
|
from .app.api import run as run_api
|
19
|
-
from .app.cli import ca_cert, config, endpoint, feature, intercept, MainGroup, request, scenario, snapshot, trace
|
19
|
+
from .app.cli import ca_cert, config, endpoint, feature, intercept, MainGroup, request, scenario, scaffold, snapshot, trace
|
20
20
|
from .app.cli.helpers.feature_flags import local, remote
|
21
21
|
from .app.settings import Settings
|
22
22
|
from .lib import logger
|
23
23
|
from .lib.orm.migrate_service import migrate as migrate_database
|
24
24
|
from .lib.utils.decode import decode
|
25
25
|
|
26
|
-
settings = Settings.instance()
|
26
|
+
settings: Settings = Settings.instance()
|
27
27
|
is_remote = remote(settings)
|
28
28
|
is_local = local(settings)
|
29
29
|
|
@@ -50,6 +50,7 @@ main.add_command(endpoint)
|
|
50
50
|
main.add_command(feature)
|
51
51
|
main.add_command(intercept)
|
52
52
|
main.add_command(request)
|
53
|
+
main.add_command(scaffold)
|
53
54
|
main.add_command(scenario)
|
54
55
|
main.add_command(snapshot)
|
55
56
|
main.add_command(trace)
|
stoobly_agent/config/data_dir.py
CHANGED
@@ -3,10 +3,11 @@ import shutil
|
|
3
3
|
|
4
4
|
from stoobly_agent.config.constants.env_vars import ENV
|
5
5
|
|
6
|
+
DATA_DIR_NAME = '.stoobly'
|
7
|
+
DB_FILE_NAME = 'stoobly_agent.sqlite3'
|
8
|
+
DB_VERSION_NAME = 'VERSION'
|
9
|
+
|
6
10
|
class DataDir:
|
7
|
-
DATA_DIR_NAME = '.stoobly'
|
8
|
-
DB_FILE_NAME = 'stoobly_agent.sqlite3'
|
9
|
-
DB_VERSION_NAME = 'VERSION'
|
10
11
|
|
11
12
|
_instances = None
|
12
13
|
|
@@ -15,10 +16,10 @@ class DataDir:
|
|
15
16
|
raise RuntimeError('Call instance() instead')
|
16
17
|
else:
|
17
18
|
if path:
|
18
|
-
self.__data_dir_path = os.path.join(path,
|
19
|
+
self.__data_dir_path = os.path.join(path, DATA_DIR_NAME)
|
19
20
|
else:
|
20
21
|
cwd = os.getcwd()
|
21
|
-
self.__data_dir_path = os.path.join(cwd,
|
22
|
+
self.__data_dir_path = os.path.join(cwd, DATA_DIR_NAME)
|
22
23
|
|
23
24
|
# If the current working directory does not contain a .stoobly folder,
|
24
25
|
# then search in the parent directories until the home directory.
|
@@ -26,12 +27,12 @@ class DataDir:
|
|
26
27
|
data_dir = self.find_data_dir(cwd)
|
27
28
|
|
28
29
|
if not data_dir:
|
29
|
-
self.__data_dir_path = os.path.join(os.path.expanduser('~'),
|
30
|
+
self.__data_dir_path = os.path.join(os.path.expanduser('~'), DATA_DIR_NAME)
|
30
31
|
else:
|
31
32
|
self.__data_dir_path = data_dir
|
32
33
|
|
33
34
|
if not os.path.exists(self.__data_dir_path):
|
34
|
-
os.
|
35
|
+
self.create(os.path.dirname(self.__data_dir_path))
|
35
36
|
|
36
37
|
@classmethod
|
37
38
|
def instance(cls, path: str = None):
|
@@ -50,10 +51,14 @@ class DataDir:
|
|
50
51
|
|
51
52
|
return cls.instance()
|
52
53
|
|
54
|
+
@property
|
55
|
+
def context_dir_path(self):
|
56
|
+
return os.path.abspath(os.path.join(self.path, '..'))
|
57
|
+
|
53
58
|
@property
|
54
59
|
def path(self):
|
55
60
|
if os.environ.get(ENV) == 'test':
|
56
|
-
test_path = os.path.join(self.__data_dir_path, 'tmp',
|
61
|
+
test_path = os.path.join(self.__data_dir_path, 'tmp', DATA_DIR_NAME)
|
57
62
|
|
58
63
|
if not os.path.exists(test_path):
|
59
64
|
os.makedirs(test_path, exist_ok=True)
|
@@ -71,6 +76,15 @@ class DataDir:
|
|
71
76
|
|
72
77
|
return tmp_dir_path
|
73
78
|
|
79
|
+
@property
|
80
|
+
def certs_dir_path(self):
|
81
|
+
certs_dir_path = os.path.join(self.path, 'certs')
|
82
|
+
|
83
|
+
if not os.path.exists(certs_dir_path):
|
84
|
+
os.mkdir(certs_dir_path)
|
85
|
+
|
86
|
+
return certs_dir_path
|
87
|
+
|
74
88
|
@property
|
75
89
|
def db_dir_path(self):
|
76
90
|
db_dir_path = os.path.join(self.path, 'db')
|
@@ -82,11 +96,11 @@ class DataDir:
|
|
82
96
|
|
83
97
|
@property
|
84
98
|
def db_file_path(self):
|
85
|
-
return os.path.join(self.db_dir_path,
|
99
|
+
return os.path.join(self.db_dir_path, DB_FILE_NAME)
|
86
100
|
|
87
101
|
@property
|
88
102
|
def db_version_path(self):
|
89
|
-
return os.path.join(self.db_dir_path,
|
103
|
+
return os.path.join(self.db_dir_path, DB_VERSION_NAME)
|
90
104
|
|
91
105
|
@property
|
92
106
|
def settings_file_path(self):
|
@@ -152,22 +166,34 @@ class DataDir:
|
|
152
166
|
|
153
167
|
def remove(self, directory_path = None):
|
154
168
|
if directory_path:
|
155
|
-
data_dir_path = os.path.join(directory_path,
|
169
|
+
data_dir_path = os.path.join(directory_path, DATA_DIR_NAME)
|
156
170
|
else:
|
157
171
|
data_dir_path = self.path
|
158
172
|
|
159
173
|
if os.path.exists(data_dir_path):
|
160
|
-
shutil.rmtree(data_dir_path)
|
174
|
+
shutil.rmtree(data_dir_path)
|
161
175
|
|
162
176
|
def create(self, directory_path = None):
|
163
177
|
if not directory_path:
|
164
178
|
directory_path = os.getcwd()
|
165
179
|
|
166
|
-
self.__data_dir_path = os.path.join(directory_path,
|
180
|
+
self.__data_dir_path = os.path.join(directory_path, DATA_DIR_NAME)
|
167
181
|
|
168
182
|
if not os.path.exists(self.__data_dir_path):
|
169
183
|
os.mkdir(self.__data_dir_path)
|
170
184
|
|
185
|
+
with open(os.path.join(self.__data_dir_path, '.gitignore'), 'w') as fp:
|
186
|
+
fp.write(
|
187
|
+
"\n".join([
|
188
|
+
'db',
|
189
|
+
'settings.yml',
|
190
|
+
os.path.join('snapshots', 'log'),
|
191
|
+
os.path.join('snapshots', 'VERSION'),
|
192
|
+
'tmp'
|
193
|
+
])
|
194
|
+
)
|
195
|
+
|
196
|
+
|
171
197
|
def find_data_dir(self, start_path: str) -> str:
|
172
198
|
# Note: these paths won't work for Windows
|
173
199
|
root_dir = os.path.abspath(os.sep)
|
@@ -175,7 +201,7 @@ class DataDir:
|
|
175
201
|
root_reached = False
|
176
202
|
|
177
203
|
while start_path != home_dir:
|
178
|
-
data_dir_path = os.path.join(start_path,
|
204
|
+
data_dir_path = os.path.join(start_path, DATA_DIR_NAME)
|
179
205
|
|
180
206
|
if os.path.exists(data_dir_path):
|
181
207
|
return data_dir_path
|
stoobly_agent/lib/logger.py
CHANGED
@@ -2,6 +2,7 @@ import logging
|
|
2
2
|
import logging.config
|
3
3
|
import os
|
4
4
|
import pdb
|
5
|
+
import sys
|
5
6
|
|
6
7
|
from typing import Literal
|
7
8
|
|
@@ -56,8 +57,8 @@ class Logger:
|
|
56
57
|
logging.config.dictConfig({'disable_existing_loggers': True, 'version': 1})
|
57
58
|
logging.basicConfig(level=logging.DEBUG)
|
58
59
|
elif log_level.lower() == WARNING:
|
59
|
-
logging.basicConfig(level=logging.WARNING)
|
60
|
+
logging.basicConfig(level=logging.WARNING, stream=sys.stderr)
|
60
61
|
elif log_level.lower() == ERROR:
|
61
|
-
logging.basicConfig(level=logging.ERROR)
|
62
|
+
logging.basicConfig(level=logging.ERROR, stream=sys.stderr)
|
62
63
|
else:
|
63
64
|
logging.basicConfig(level=logging.INFO)
|
@@ -1 +1 @@
|
|
1
|
-
0.34.
|
1
|
+
0.34.13
|
@@ -5,7 +5,7 @@ import pytest
|
|
5
5
|
|
6
6
|
from stoobly_agent.config.constants.env_vars import ENV
|
7
7
|
from stoobly_agent.config.constants.mode import NONE
|
8
|
-
from stoobly_agent.config.data_dir import DataDir
|
8
|
+
from stoobly_agent.config.data_dir import DataDir, DATA_DIR_NAME
|
9
9
|
from stoobly_agent.test.test_helper import reset
|
10
10
|
|
11
11
|
|
@@ -25,7 +25,7 @@ class TestDataDir():
|
|
25
25
|
def test_in_home(self, original_cwd: str, home_dir: str):
|
26
26
|
# A previous test can put us in 'stoobly_agent/test/app/models/schemas/.stoobly'
|
27
27
|
os.chdir(original_cwd)
|
28
|
-
data_dir_path = os.path.join(home_dir,
|
28
|
+
data_dir_path = os.path.join(home_dir, DATA_DIR_NAME)
|
29
29
|
os.environ[ENV] = NONE
|
30
30
|
|
31
31
|
result = DataDir.instance().path
|
@@ -38,7 +38,7 @@ class TestDataDir():
|
|
38
38
|
|
39
39
|
temp_dir = os.path.join(original_cwd, 'tmp')
|
40
40
|
nested_temp_dir = os.path.join(temp_dir, 'tmp-nested')
|
41
|
-
data_dir_path = os.path.join(nested_temp_dir,
|
41
|
+
data_dir_path = os.path.join(nested_temp_dir, DATA_DIR_NAME)
|
42
42
|
os.makedirs(data_dir_path)
|
43
43
|
|
44
44
|
# Go into nested folder
|
@@ -68,7 +68,7 @@ class TestDataDir():
|
|
68
68
|
os.makedirs(nested_dir3)
|
69
69
|
|
70
70
|
# Create .stoobly folder in the nested structure
|
71
|
-
data_dir_path = os.path.join(nested_dir1,
|
71
|
+
data_dir_path = os.path.join(nested_dir1, DATA_DIR_NAME)
|
72
72
|
os.makedirs(data_dir_path)
|
73
73
|
|
74
74
|
# Go into dir3
|
@@ -1,27 +1,28 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: stoobly-agent
|
3
|
-
Version: 0.
|
3
|
+
Version: 1.0.0
|
4
4
|
Summary: Record, mock, and test HTTP(s) requests. CLI agent for Stoobly
|
5
5
|
License: Apache-2.0
|
6
6
|
Author: Matt Le
|
7
7
|
Author-email: themathewle@gmail.com
|
8
|
-
Requires-Python: >=3.
|
8
|
+
Requires-Python: >=3.10,<4.0
|
9
9
|
Classifier: License :: OSI Approved :: Apache Software License
|
10
10
|
Classifier: Programming Language :: Python :: 3
|
11
|
-
Classifier: Programming Language :: Python :: 3.8
|
12
|
-
Classifier: Programming Language :: Python :: 3.9
|
13
11
|
Classifier: Programming Language :: Python :: 3.10
|
14
12
|
Classifier: Programming Language :: Python :: 3.11
|
15
13
|
Classifier: Programming Language :: Python :: 3.12
|
14
|
+
Classifier: Programming Language :: Python :: 3.13
|
16
15
|
Requires-Dist: click (>=7.0.0,<8.0.0)
|
17
16
|
Requires-Dist: diff-match-patch (>=20230430,<20230431)
|
18
17
|
Requires-Dist: distro (>=1.6.0,<1.7.0)
|
18
|
+
Requires-Dist: dnspython (>=2.6.1,<2.7.0)
|
19
19
|
Requires-Dist: httptools (>=0.4.0)
|
20
20
|
Requires-Dist: jmespath (>=1.0.0)
|
21
21
|
Requires-Dist: mergedeep (>=1.3.0,<1.3.4)
|
22
|
-
Requires-Dist: mitmproxy (>=
|
22
|
+
Requires-Dist: mitmproxy (>=10.0.0,<11.0.0)
|
23
23
|
Requires-Dist: multipart (>=0.2.5,<0.3.0)
|
24
|
-
Requires-Dist: openapi-core (>=0.
|
24
|
+
Requires-Dist: openapi-core (>=0.19.0,<0.20.0)
|
25
|
+
Requires-Dist: python-dotenv (>=1.0.1,<2.0.0)
|
25
26
|
Requires-Dist: pyyaml (>=6.0.1)
|
26
27
|
Requires-Dist: requests (>=2.31.0)
|
27
28
|
Requires-Dist: stoobly_orator (>=0.9.12)
|
@@ -55,7 +56,7 @@ See our docs for more detailed information! https://docs.stoobly.com
|
|
55
56
|
|
56
57
|
## Prerequisite
|
57
58
|
|
58
|
-
- Python
|
59
|
+
- Python 3.10, 3.11, 3.12
|
59
60
|
|
60
61
|
## Installation
|
61
62
|
|