stoobly-agent 1.10.5__py3-none-any.whl → 1.11.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/intercept_cli.py +1 -1
- stoobly_agent/app/cli/request_cli.py +19 -1
- stoobly_agent/app/cli/scaffold/docker/service/builder.py +3 -6
- stoobly_agent/app/cli/scaffold/docker/workflow/builder.py +2 -1
- stoobly_agent/app/cli/scaffold/docker/workflow/mock_decorator.py +2 -0
- stoobly_agent/app/cli/scaffold/docker/workflow/run_command.py +5 -0
- stoobly_agent/app/cli/scaffold/local/service/builder.py +3 -10
- stoobly_agent/app/cli/scaffold/local/workflow/builder.py +2 -4
- stoobly_agent/app/cli/scaffold/local/workflow/run_command.py +7 -0
- stoobly_agent/app/cli/scaffold/service_workflow_validate_command.py +1 -1
- stoobly_agent/app/cli/scaffold/templates/app/.Dockerfile.context +2 -2
- stoobly_agent/app/cli/scaffold/templates/app/.Makefile +21 -11
- stoobly_agent/app/cli/scaffold/templates/app/.docker-compose.base.yml +2 -0
- stoobly_agent/app/cli/scaffold/templates/app/stoobly-ui/mock/.docker-compose.yml +2 -2
- stoobly_agent/app/cli/scaffold/templates/app/stoobly-ui/record/.docker-compose.yml +2 -2
- stoobly_agent/app/cli/scaffold/templates/build/workflows/exec/request/log/.list +5 -0
- stoobly_agent/app/cli/scaffold/workflow_run_command.py +4 -5
- stoobly_agent/app/proxy/handle_mock_service.py +7 -2
- stoobly_agent/app/proxy/run.py +4 -1
- stoobly_agent/cli.py +16 -1
- stoobly_agent/config/constants/custom_headers.py +1 -0
- stoobly_agent/lib/intercepted_requests_logger.py +382 -0
- stoobly_agent/test/app/cli/scaffold/docker/e2e_test.py +2 -2
- stoobly_agent/test/app/models/schemas/.stoobly/db/VERSION +1 -1
- {stoobly_agent-1.10.5.dist-info → stoobly_agent-1.11.0.dist-info}/METADATA +1 -1
- {stoobly_agent-1.10.5.dist-info → stoobly_agent-1.11.0.dist-info}/RECORD +30 -28
- {stoobly_agent-1.10.5.dist-info → stoobly_agent-1.11.0.dist-info}/WHEEL +0 -0
- {stoobly_agent-1.10.5.dist-info → stoobly_agent-1.11.0.dist-info}/entry_points.txt +0 -0
- {stoobly_agent-1.10.5.dist-info → stoobly_agent-1.11.0.dist-info}/licenses/LICENSE +0 -0
stoobly_agent/__init__.py
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
COMMAND = 'stoobly-agent'
|
|
2
|
-
VERSION = '1.
|
|
2
|
+
VERSION = '1.11.0'
|
|
@@ -128,7 +128,7 @@ def configure(**kwargs):
|
|
|
128
128
|
active_mode = settings.proxy.intercept.mode
|
|
129
129
|
valid_policies = __get_policy_options(active_mode)
|
|
130
130
|
|
|
131
|
-
if
|
|
131
|
+
if kwargs['policy'] not in valid_policies:
|
|
132
132
|
print("Error: Valid policies for {active_mode} are {valid_policies}", file=sys.stderr)
|
|
133
133
|
sys.exit(1)
|
|
134
134
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import click
|
|
2
2
|
|
|
3
3
|
from stoobly_agent.app.cli.helpers.handle_replay_service import BODY_FORMAT, JSON_FORMAT
|
|
4
|
+
from stoobly_agent.lib.intercepted_requests_logger import InterceptedRequestsLogger
|
|
4
5
|
from stoobly_agent.app.models.factories.resource.local_db.helpers.log_event import DELETE_ACTION, PUT_ACTION
|
|
5
6
|
from stoobly_agent.app.settings import Settings
|
|
6
7
|
from stoobly_agent.config.constants import alias_resolve_strategy, test_filter, test_output_level, test_strategy
|
|
@@ -170,4 +171,21 @@ def get(**kwargs) -> None:
|
|
|
170
171
|
def query(**kwargs):
|
|
171
172
|
query_handler(kwargs)
|
|
172
173
|
|
|
173
|
-
|
|
174
|
+
@click.group(
|
|
175
|
+
epilog="Run 'stoobly-agent request log COMMAND --help' for more information on a command.",
|
|
176
|
+
help="Manage intercepted requests logs"
|
|
177
|
+
)
|
|
178
|
+
@click.pass_context
|
|
179
|
+
def log(ctx):
|
|
180
|
+
pass
|
|
181
|
+
|
|
182
|
+
@log.command(name="list", help="List intercepted requests log entries")
|
|
183
|
+
def log_list(**kwargs):
|
|
184
|
+
InterceptedRequestsLogger.dump_logs()
|
|
185
|
+
|
|
186
|
+
@log.command(name="delete", help="Delete intercepted requests log entries")
|
|
187
|
+
def log_delete(**kwargs):
|
|
188
|
+
InterceptedRequestsLogger.truncate()
|
|
189
|
+
|
|
190
|
+
request.add_command(response)
|
|
191
|
+
request.add_command(log)
|
|
@@ -1,21 +1,18 @@
|
|
|
1
1
|
import os
|
|
2
2
|
import pdb
|
|
3
3
|
|
|
4
|
-
from typing import List
|
|
5
|
-
|
|
6
4
|
from stoobly_agent.config.data_dir import DATA_DIR_NAME
|
|
7
5
|
|
|
8
6
|
from ...app_config import AppConfig
|
|
9
7
|
from ...constants import (
|
|
10
|
-
APP_DIR, SERVICES_NAMESPACE,
|
|
8
|
+
APP_DIR, SERVICES_NAMESPACE,
|
|
11
9
|
SERVICE_HOSTNAME, SERVICE_HOSTNAME_ENV,
|
|
12
|
-
SERVICE_NAME, SERVICE_NAME_ENV,
|
|
13
10
|
SERVICE_ID,
|
|
14
|
-
SERVICE_PORT, SERVICE_PORT_ENV,
|
|
11
|
+
SERVICE_PORT, SERVICE_PORT_ENV,
|
|
15
12
|
SERVICE_SCHEME, SERVICE_SCHEME_ENV,
|
|
16
13
|
SERVICE_UPSTREAM_HOSTNAME, SERVICE_UPSTREAM_HOSTNAME_ENV, SERVICE_UPSTREAM_PORT, SERVICE_UPSTREAM_PORT_ENV, SERVICE_UPSTREAM_SCHEME, SERVICE_UPSTREAM_SCHEME_ENV,
|
|
17
14
|
STOOBLY_HOME_DIR,
|
|
18
|
-
|
|
15
|
+
WORKFLOW_SCRIPTS, WORKFLOW_TEMPLATE
|
|
19
16
|
)
|
|
20
17
|
from ...service_config import ServiceConfig
|
|
21
18
|
from ...local.service.builder import ServiceBuilder
|
|
@@ -4,7 +4,7 @@ import pdb
|
|
|
4
4
|
from typing import List, Union
|
|
5
5
|
|
|
6
6
|
from ...constants import (
|
|
7
|
-
WORKFLOW_CONTAINER_CONFIGURE_TEMPLATE, WORKFLOW_CONTAINER_INIT_TEMPLATE, WORKFLOW_CONTAINER_PROXY_TEMPLATE, WORKFLOW_NAME
|
|
7
|
+
WORKFLOW_CONTAINER_CONFIGURE_TEMPLATE, WORKFLOW_CONTAINER_INIT_TEMPLATE, WORKFLOW_CONTAINER_PROXY_TEMPLATE, WORKFLOW_NAME
|
|
8
8
|
)
|
|
9
9
|
from ...local.workflow.builder import WorkflowBuilder
|
|
10
10
|
from ..builder import Builder
|
|
@@ -13,6 +13,7 @@ from ..service.builder import DockerServiceBuilder
|
|
|
13
13
|
from .mock_decorator import MockDecorator
|
|
14
14
|
from .reverse_proxy_decorator import ReverseProxyDecorator
|
|
15
15
|
|
|
16
|
+
|
|
16
17
|
class DockerWorkflowBuilder(Builder, WorkflowBuilder):
|
|
17
18
|
|
|
18
19
|
def __init__(self, workflow_path: str, service_builder: DockerServiceBuilder):
|
|
@@ -34,6 +34,8 @@ class MockDecorator(CommandDecorator):
|
|
|
34
34
|
command.append('--certs')
|
|
35
35
|
command.append(os.path.join(STOOBLY_CERTS_DIR, f"{SERVICE_HOSTNAME}-joined.pem"))
|
|
36
36
|
|
|
37
|
+
command.append('--request-log-enable')
|
|
38
|
+
|
|
37
39
|
services = self.workflow_builder.services
|
|
38
40
|
proxy_name = self.workflow_builder.proxy
|
|
39
41
|
proxy_service = services.get(proxy_name) or {}
|
|
@@ -435,6 +435,11 @@ class DockerWorkflowRunCommand(WorkflowRunCommand):
|
|
|
435
435
|
if folder != self.workflow_name:
|
|
436
436
|
workflow_name = file_name.split(self.timestamp_file_extension)[0]
|
|
437
437
|
|
|
438
|
+
# If the workflow is namespaced, allow it to run at the same time
|
|
439
|
+
# Same workflow with same namespace is allowed, workflow will be restarted
|
|
440
|
+
if workflow_name == self.workflow_name:
|
|
441
|
+
return
|
|
442
|
+
|
|
438
443
|
Logger.instance(LOG_ID).error(f"Workflow '{workflow_name}' is running, please stop it first.")
|
|
439
444
|
|
|
440
445
|
if folder != workflow_name:
|
|
@@ -3,17 +3,10 @@ from typing import List
|
|
|
3
3
|
|
|
4
4
|
from stoobly_agent.config.data_dir import DATA_DIR_NAME
|
|
5
5
|
|
|
6
|
-
from ...app_config import AppConfig
|
|
7
6
|
from ...constants import (
|
|
8
|
-
|
|
9
|
-
SERVICE_HOSTNAME, SERVICE_HOSTNAME_ENV,
|
|
10
|
-
SERVICE_NAME, SERVICE_NAME_ENV,
|
|
11
|
-
SERVICE_ID,
|
|
12
|
-
SERVICE_PORT, SERVICE_PORT_ENV,
|
|
13
|
-
SERVICE_SCHEME, SERVICE_SCHEME_ENV,
|
|
14
|
-
SERVICE_UPSTREAM_HOSTNAME, SERVICE_UPSTREAM_HOSTNAME_ENV, SERVICE_UPSTREAM_PORT, SERVICE_UPSTREAM_PORT_ENV, SERVICE_UPSTREAM_SCHEME, SERVICE_UPSTREAM_SCHEME_ENV,
|
|
7
|
+
SERVICES_NAMESPACE, SERVICE_NAME, SERVICE_NAME_ENV,
|
|
15
8
|
STOOBLY_HOME_DIR,
|
|
16
|
-
WORKFLOW_NAME, WORKFLOW_NAME_ENV,
|
|
9
|
+
WORKFLOW_NAME, WORKFLOW_NAME_ENV, WORKFLOW_NAMESPACE_ENV
|
|
17
10
|
)
|
|
18
11
|
from ...service_config import ServiceConfig
|
|
19
12
|
|
|
@@ -23,7 +16,7 @@ class ServiceBuilder():
|
|
|
23
16
|
self.__config = config
|
|
24
17
|
self.__dir_path = config.dir
|
|
25
18
|
self.__upstream_port = None
|
|
26
|
-
self.__env = [SERVICE_NAME_ENV, WORKFLOW_NAME_ENV]
|
|
19
|
+
self.__env = [SERVICE_NAME_ENV, WORKFLOW_NAME_ENV, WORKFLOW_NAMESPACE_ENV]
|
|
27
20
|
self.__service_name = os.path.basename(config.dir)
|
|
28
21
|
self.__working_dir = os.path.join(
|
|
29
22
|
STOOBLY_HOME_DIR, DATA_DIR_NAME, SERVICES_NAMESPACE, SERVICE_NAME, WORKFLOW_NAME
|
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
import os
|
|
2
2
|
|
|
3
|
-
from
|
|
4
|
-
|
|
5
|
-
from ...constants import SERVICE_NAME_ENV, WORKFLOW_NAME_ENV
|
|
3
|
+
from ...constants import SERVICE_NAME_ENV, WORKFLOW_NAME_ENV, WORKFLOW_NAMESPACE_ENV
|
|
6
4
|
from ..service.builder import ServiceBuilder
|
|
7
5
|
|
|
8
6
|
class WorkflowBuilder():
|
|
9
7
|
|
|
10
8
|
def __init__(self, workflow_path: str, service_builder: ServiceBuilder):
|
|
11
|
-
self._env = [SERVICE_NAME_ENV, WORKFLOW_NAME_ENV]
|
|
9
|
+
self._env = [SERVICE_NAME_ENV, WORKFLOW_NAME_ENV, WORKFLOW_NAMESPACE_ENV]
|
|
12
10
|
self._service_builder = service_builder
|
|
13
11
|
self._workflow_name = os.path.basename(workflow_path)
|
|
14
12
|
|
|
@@ -283,9 +283,16 @@ class LocalWorkflowRunCommand(WorkflowRunCommand):
|
|
|
283
283
|
else:
|
|
284
284
|
file_name = os.path.basename(pid_file_path)
|
|
285
285
|
workflow_name = self.workflow_name
|
|
286
|
+
|
|
287
|
+
# If the folder is not the same as the workflow name, then the workflow is namespaced, get the real workflow name
|
|
286
288
|
if folder != self.workflow_name:
|
|
287
289
|
workflow_name = file_name.split(self.pid_file_extension)[0]
|
|
288
290
|
|
|
291
|
+
# If the workflow is namespaced, allow it to run at the same time
|
|
292
|
+
# Same workflow with same namespace is covered by pid_file_path check
|
|
293
|
+
if workflow_name == self.workflow_name:
|
|
294
|
+
return
|
|
295
|
+
|
|
289
296
|
Logger.instance(LOG_ID).error(f"Workflow {workflow_name} is already running with PID {pid}")
|
|
290
297
|
|
|
291
298
|
if folder != workflow_name:
|
|
@@ -135,7 +135,7 @@ class ServiceWorkflowValidateCommand(ServiceCommand, ValidateCommand):
|
|
|
135
135
|
# connection is working, but we haven't recorded anything yet
|
|
136
136
|
logs = output.decode('ascii')
|
|
137
137
|
if logs != '200' and logs != '499':
|
|
138
|
-
raise ScaffoldValidateException(f"Error reaching {url} from inside Docker network")
|
|
138
|
+
raise ScaffoldValidateException(f"Error reaching {url} from inside Docker network with logs: \n{logs}")
|
|
139
139
|
|
|
140
140
|
# Check public folder exists in container
|
|
141
141
|
def validate_public_folder(self, container: Container):
|
|
@@ -110,10 +110,11 @@ intercept/disable:
|
|
|
110
110
|
intercept/enable:
|
|
111
111
|
@export EXEC_COMMAND=intercept/.enable EXEC_OPTIONS="" EXEC_ARGS=$(scenario_key) && \
|
|
112
112
|
$(stoobly_exec)
|
|
113
|
-
mock: workflow/mock ca-cert/install workflow/up nameservers workflow/hostname/install workflow/run
|
|
113
|
+
mock: workflow/mock ca-cert/install workflow/up nameservers workflow/hostname/install workflow/up/run
|
|
114
|
+
mock/down: workflow/mock workflow/down workflow/down/run workflow/hostname/uninstall
|
|
115
|
+
mock/logs: workflow/mock workflow/logs workflow/logs/run
|
|
116
|
+
mock/report: workflow/mock workflow/report
|
|
114
117
|
mock/services: workflow/mock workflow/services
|
|
115
|
-
mock/logs: workflow/mock workflow/logs workflow/run
|
|
116
|
-
mock/down: workflow/mock workflow/down workflow/run workflow/hostname/uninstall
|
|
117
118
|
pipx/install:
|
|
118
119
|
@if ! command -v pipx >/dev/null 2>&1; then \
|
|
119
120
|
echo "pipx is not installed. Installing pipx..."; \
|
|
@@ -124,10 +125,11 @@ python/validate:
|
|
|
124
125
|
echo "Error: Python 3.10, 3.11, or 3.12 is required."; \
|
|
125
126
|
exit 1; \
|
|
126
127
|
fi
|
|
127
|
-
record: workflow/record ca-cert/install workflow/up nameservers workflow/hostname/install workflow/run
|
|
128
|
-
record/down: workflow/record workflow/down workflow/run workflow/hostname/uninstall
|
|
128
|
+
record: workflow/record ca-cert/install workflow/up nameservers workflow/hostname/install workflow/up/run
|
|
129
|
+
record/down: workflow/record workflow/down workflow/down/run workflow/hostname/uninstall
|
|
130
|
+
record/logs: workflow/record workflow/logs workflow/logs/run
|
|
131
|
+
record/report: workflow/record workflow/report
|
|
129
132
|
record/services: workflow/record workflow/services
|
|
130
|
-
record/logs: workflow/record workflow/logs workflow/run
|
|
131
133
|
scenario/create:
|
|
132
134
|
# Create a scenario
|
|
133
135
|
@export EXEC_COMMAND=scenario/.create EXEC_OPTIONS="$(options)" EXEC_ARGS="$(name)" && \
|
|
@@ -157,15 +159,18 @@ stoobly/install: python/validate pipx/install
|
|
|
157
159
|
echo "stoobly-agent not found. Installing..."; \
|
|
158
160
|
pipx install stoobly-agent || { echo "Failed to install stoobly-agent"; exit 1; }; \
|
|
159
161
|
fi
|
|
160
|
-
test: workflow/test workflow/up workflow/run
|
|
162
|
+
test: workflow/test workflow/up workflow/up/run
|
|
163
|
+
test/down: workflow/test workflow/down workflow/down/run
|
|
164
|
+
test/logs: workflow/test workflow/logs workflow/logs/run
|
|
165
|
+
test/report: workflow/test workflow/report
|
|
161
166
|
test/services: workflow/test workflow/services
|
|
162
|
-
test/logs: workflow/test workflow/logs workflow/run
|
|
163
|
-
test/down: workflow/test workflow/down workflow/run
|
|
164
167
|
tmpdir:
|
|
165
168
|
@mkdir -p $(app_tmp_dir)
|
|
166
169
|
workflow/down: dotenv
|
|
167
170
|
@export EXEC_COMMAND=scaffold/.down EXEC_OPTIONS="$(workflow_down_options) $(workflow_run_options) $(options)" EXEC_ARGS="$(workflow)" && \
|
|
168
171
|
$(stoobly_exec_run)
|
|
172
|
+
workflow/down/run:
|
|
173
|
+
@bash "$(app_dir)/$(workflow_script)"
|
|
169
174
|
workflow/hostname: stoobly/install
|
|
170
175
|
@if [ -n "$$STOOBLY_HOSTNAME_INSTALL_CONFIRM" ]; then \
|
|
171
176
|
confirm="$$STOOBLY_HOSTNAME_INSTALL_CONFIRM"; \
|
|
@@ -187,14 +192,17 @@ workflow/hostname/uninstall: action/uninstall workflow/hostname
|
|
|
187
192
|
workflow/logs:
|
|
188
193
|
@export EXEC_COMMAND=scaffold/.logs EXEC_OPTIONS="$(workflow_log_options) $(workflow_run_options) $(options)" EXEC_ARGS="$(workflow)" && \
|
|
189
194
|
$(stoobly_exec_run)
|
|
195
|
+
workflow/logs/run:
|
|
196
|
+
@bash "$(app_dir)/$(workflow_script)"
|
|
190
197
|
workflow/mock:
|
|
191
198
|
$(eval workflow=mock)
|
|
192
199
|
workflow/namespace: tmpdir
|
|
193
200
|
@mkdir -p $(workflow_namespace_dir)
|
|
194
201
|
workflow/record:
|
|
195
202
|
$(eval workflow=record)
|
|
196
|
-
workflow/
|
|
197
|
-
@
|
|
203
|
+
workflow/report:
|
|
204
|
+
@export EXEC_COMMAND=request/log/.list EXEC_OPTIONS="$(options)" EXEC_ARGS="" && \
|
|
205
|
+
$(stoobly_exec)
|
|
198
206
|
workflow/services:
|
|
199
207
|
@export EXEC_COMMAND=scaffold/.services EXEC_OPTIONS="$(workflow_service_options) $(options)" EXEC_ARGS="$(workflow)" && \
|
|
200
208
|
$(stoobly_exec_run)
|
|
@@ -203,3 +211,5 @@ workflow/test:
|
|
|
203
211
|
workflow/up: dotenv
|
|
204
212
|
@export EXEC_COMMAND=scaffold/.up EXEC_OPTIONS="$(workflow_up_options) $(workflow_run_options) $(options)" EXEC_ARGS="$(workflow)" && \
|
|
205
213
|
$(stoobly_exec_run)
|
|
214
|
+
workflow/up/run:
|
|
215
|
+
@bash "$(app_dir)/$(workflow_script)"
|
|
@@ -4,6 +4,8 @@ services:
|
|
|
4
4
|
service: stoobly_base
|
|
5
5
|
volumes:
|
|
6
6
|
- ${CONTEXT_DIR}/.stoobly:/home/stoobly/.stoobly
|
|
7
|
+
- ${APP_DIR}/.stoobly/ca_certs:/home/stoobly/.stoobly/ca_certs
|
|
8
|
+
- ${APP_DIR}/.stoobly/certs:/home/stoobly/.stoobly/certs
|
|
7
9
|
- ${APP_DIR}/.stoobly/services:/home/stoobly/.stoobly/services
|
|
8
10
|
stoobly_base:
|
|
9
11
|
image: stoobly.${USER_ID}
|
|
@@ -8,8 +8,8 @@ from stoobly_agent.config.data_dir import DataDir
|
|
|
8
8
|
|
|
9
9
|
from .app import App
|
|
10
10
|
from .constants import (
|
|
11
|
-
APP_DIR_ENV, APP_NETWORK_ENV,
|
|
12
|
-
SERVICE_DNS_ENV, SERVICE_NAME_ENV, SERVICE_SCRIPTS_DIR, SERVICE_SCRIPTS_ENV, USER_ID_ENV,
|
|
11
|
+
APP_DIR_ENV, APP_NETWORK_ENV, CA_CERTS_DIR_ENV, CERTS_DIR_ENV, CONTEXT_DIR_ENV,
|
|
12
|
+
SERVICE_DNS_ENV, SERVICE_NAME_ENV, SERVICE_SCRIPTS_DIR, SERVICE_SCRIPTS_ENV, USER_ID_ENV,
|
|
13
13
|
WORKFLOW_NAME_ENV, WORKFLOW_NAMESPACE_ENV, WORKFLOW_SCRIPTS_DIR, WORKFLOW_SCRIPTS_ENV, WORKFLOW_TEMPLATE_ENV
|
|
14
14
|
)
|
|
15
15
|
|
|
@@ -139,9 +139,8 @@ class WorkflowRunCommand(WorkflowCommand):
|
|
|
139
139
|
_config[WORKFLOW_NAME_ENV] = self.workflow_name
|
|
140
140
|
_config[WORKFLOW_SCRIPTS_ENV] = WORKFLOW_SCRIPTS_DIR
|
|
141
141
|
_config[WORKFLOW_TEMPLATE_ENV] = self.workflow_name
|
|
142
|
-
|
|
143
|
-
if namespace
|
|
144
|
-
_config[WORKFLOW_NAMESPACE_ENV] = namespace
|
|
142
|
+
# Default to the workflow name if a namespace isn't given
|
|
143
|
+
_config[WORKFLOW_NAMESPACE_ENV] = namespace if namespace else _config[WORKFLOW_NAME_ENV]
|
|
145
144
|
|
|
146
145
|
if self.network:
|
|
147
146
|
_config[APP_NETWORK_ENV] = self.network
|
|
@@ -10,8 +10,8 @@ from stoobly_agent.app.models.request_model import RequestModel
|
|
|
10
10
|
from stoobly_agent.app.proxy.mitmproxy.request_facade import MitmproxyRequestFacade
|
|
11
11
|
from stoobly_agent.app.proxy.utils.rewrite_rules_to_ignored_components_service import rewrite_rules_to_ignored_components
|
|
12
12
|
from stoobly_agent.config.constants import custom_headers, env_vars, lifecycle_hooks, mock_policy, request_origin
|
|
13
|
-
from stoobly_agent.lib.logger import bcolors, Logger
|
|
14
|
-
|
|
13
|
+
from stoobly_agent.lib.logger import bcolors, Logger, DEBUG, INFO, WARNING, ERROR
|
|
14
|
+
from stoobly_agent.lib.intercepted_requests_logger import InterceptedRequestsLogger
|
|
15
15
|
from .constants import custom_response_codes
|
|
16
16
|
from .mock.context import MockContext
|
|
17
17
|
from .mock.eval_fixtures_service import eval_fixtures
|
|
@@ -153,6 +153,9 @@ def handle_response_mock(context: MockContext):
|
|
|
153
153
|
def __handle_mock_failure(context: MockContext) -> None:
|
|
154
154
|
flow = context.flow
|
|
155
155
|
request = flow.request
|
|
156
|
+
response = context.response
|
|
157
|
+
|
|
158
|
+
InterceptedRequestsLogger.error("Mock failure", request=request, response=response)
|
|
156
159
|
|
|
157
160
|
if request.method.upper() != 'OPTIONS':
|
|
158
161
|
return False
|
|
@@ -187,10 +190,12 @@ def __handle_mock_success(context: MockContext) -> None:
|
|
|
187
190
|
request_key = response.headers.get(custom_headers.MOCK_REQUEST_KEY)
|
|
188
191
|
if request_key:
|
|
189
192
|
Logger.instance(LOG_ID).info(f"{bcolors.OKBLUE}Mocked{bcolors.ENDC} {request.url} -> {request_key}")
|
|
193
|
+
InterceptedRequestsLogger.info("Mock success", request=request, response=response, request_key=request_key)
|
|
190
194
|
|
|
191
195
|
fixture_path = response.headers.get(custom_headers.MOCK_FIXTURE_PATH)
|
|
192
196
|
if fixture_path:
|
|
193
197
|
Logger.instance(LOG_ID).info(f"{bcolors.OKBLUE}Mocked{bcolors.ENDC} {request.url} -> {fixture_path}")
|
|
198
|
+
InterceptedRequestsLogger.info("Mock success", request=request, response=response, fixture_path=fixture_path)
|
|
194
199
|
|
|
195
200
|
if os.environ.get(env_vars.AGENT_SIMULATE_LATENCY):
|
|
196
201
|
response = context.response
|
stoobly_agent/app/proxy/run.py
CHANGED
|
@@ -138,4 +138,7 @@ def __filter_options(options):
|
|
|
138
138
|
del options['log_level']
|
|
139
139
|
del options['proxy_host']
|
|
140
140
|
del options['proxy_mode']
|
|
141
|
-
del options['proxy_port']
|
|
141
|
+
del options['proxy_port']
|
|
142
|
+
del options['request_log_enable']
|
|
143
|
+
del options['request_log_level']
|
|
144
|
+
del options['request_log_truncate']
|
stoobly_agent/cli.py
CHANGED
|
@@ -12,9 +12,9 @@ from stoobly_agent.app.cli.helpers.validations import validate_project_key, vali
|
|
|
12
12
|
from stoobly_agent.app.cli.intercept_cli import mode_options
|
|
13
13
|
from stoobly_agent.app.proxy.constants import custom_response_codes
|
|
14
14
|
from stoobly_agent.app.proxy.replay.replay_request_service import replay as replay_request
|
|
15
|
-
from stoobly_agent.app.settings.constants import intercept_mode
|
|
16
15
|
from stoobly_agent.config.constants import env_vars, mode
|
|
17
16
|
from stoobly_agent.config.data_dir import DataDir
|
|
17
|
+
from stoobly_agent.lib.intercepted_requests_logger import InterceptedRequestsLogger
|
|
18
18
|
from stoobly_agent.lib.logger import Logger
|
|
19
19
|
from stoobly_agent.lib.utils.conditional_decorator import ConditionalDecorator
|
|
20
20
|
|
|
@@ -123,6 +123,9 @@ def init(**kwargs):
|
|
|
123
123
|
@click.option('--proxy-port', default=8080, type=click.IntRange(1, 65535), help='Proxy service port.')
|
|
124
124
|
@click.option('--public-directory-path', multiple=True, help='Path to public files. Used for mocking requests. Can take the form <FOLDER-PATH>[:<ORIGIN>].')
|
|
125
125
|
@click.option('--response-fixtures-path', multiple=True, help='Path to response fixtures yaml. Used for mocking requests. Can take the form <FILE-PATH>[:<ORIGIN>].')
|
|
126
|
+
@click.option('--request-log-enable', is_flag=True, default=False, required=False, help='Enable intercepted requests logging')
|
|
127
|
+
@click.option('--request-log-level', default=logger.INFO, type=click.Choice([logger.DEBUG, logger.INFO, logger.WARNING, logger.ERROR]), help='Log level for intercepted requests.')
|
|
128
|
+
@click.option('--request-log-truncate', is_flag=True, default=True, required=False, help='Truncate the intercepted requests log')
|
|
126
129
|
@click.option('--ssl-insecure', is_flag=True, default=False, help='Do not verify upstream server SSL/TLS certificates.')
|
|
127
130
|
@click.option('--ui-host', default='0.0.0.0', help='Address to bind UI to.')
|
|
128
131
|
@click.option('--ui-port', default=4200, type=click.IntRange(1, 65535), help='UI service port.')
|
|
@@ -180,6 +183,18 @@ def run(**kwargs):
|
|
|
180
183
|
os.environ[env_vars.AGENT_PROXY_URL] = proxy_url
|
|
181
184
|
settings.proxy.url = proxy_url
|
|
182
185
|
|
|
186
|
+
if kwargs.get('request_log_enable'):
|
|
187
|
+
# If truncating, do that first (it handles enable internally)
|
|
188
|
+
if kwargs.get('request_log_truncate'):
|
|
189
|
+
InterceptedRequestsLogger.truncate()
|
|
190
|
+
else:
|
|
191
|
+
InterceptedRequestsLogger.enable_logger_file()
|
|
192
|
+
|
|
193
|
+
# Set log level after logger is enabled
|
|
194
|
+
request_log_level = kwargs.get('request_log_level')
|
|
195
|
+
if request_log_level:
|
|
196
|
+
InterceptedRequestsLogger.set_log_level(request_log_level)
|
|
197
|
+
|
|
183
198
|
if kwargs.get('detached'):
|
|
184
199
|
# Run in detached mode with output redirection
|
|
185
200
|
import subprocess
|
|
@@ -25,6 +25,7 @@ SESSION_ID = 'X-Stoobly-Session-Id'
|
|
|
25
25
|
SERVICE_URL = 'X-Stoobly-Service-Url'
|
|
26
26
|
TEST_FILTER = 'X-Stoobly-Test-Filter'
|
|
27
27
|
TEST_ID = 'X-Stoobly-Test-Id'
|
|
28
|
+
TEST_NAME = 'X-Stoobly-Test-Name'
|
|
28
29
|
TEST_POLICY = 'X-Stoobly-Test-Policy'
|
|
29
30
|
TEST_SAVE_RESULTS = 'X-Stoobly-Test-Save-Results'
|
|
30
31
|
TEST_SKIP = 'X-Stoobly-Test-Skip'
|
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
|
|
2
|
+
import os
|
|
3
|
+
import logging
|
|
4
|
+
import json
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
from typing import Final
|
|
7
|
+
|
|
8
|
+
from mitmproxy.http import Request as MitmproxyRequest
|
|
9
|
+
from requests import Response
|
|
10
|
+
from stoobly_agent.app.cli.scaffold.constants import WORKFLOW_NAMESPACE_ENV, SERVICE_NAME_ENV
|
|
11
|
+
from stoobly_agent.app.settings import Settings
|
|
12
|
+
from stoobly_agent.app.proxy.intercept_settings import InterceptSettings
|
|
13
|
+
from stoobly_agent.config.data_dir import DataDir
|
|
14
|
+
from stoobly_agent.lib.api.keys.scenario_key import ScenarioKey, InvalidScenarioKey
|
|
15
|
+
from stoobly_agent.lib.api.keys.request_key import RequestKey, InvalidRequestKey
|
|
16
|
+
from stoobly_agent.lib.orm.scenario import Scenario
|
|
17
|
+
from stoobly_agent.lib.orm.request import Request
|
|
18
|
+
from stoobly_agent.lib.logger import DEBUG, ERROR, INFO, WARNING, Logger
|
|
19
|
+
from stoobly_agent.config.constants import custom_headers
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class InterceptedRequestsLogger():
|
|
23
|
+
__LOG_ID: Final[str] = "InterceptedRequestsLogger"
|
|
24
|
+
__logger: Logger = Logger.instance(__LOG_ID)
|
|
25
|
+
|
|
26
|
+
__settings: Settings = Settings.instance()
|
|
27
|
+
__NAMESPACE: str = os.environ.get(WORKFLOW_NAMESPACE_ENV, __settings.proxy.intercept.mode)
|
|
28
|
+
__file_path: str = None
|
|
29
|
+
__previous_scenario_key: str = None
|
|
30
|
+
|
|
31
|
+
# Initialize logger as disabled by default
|
|
32
|
+
__logger.disabled = True
|
|
33
|
+
|
|
34
|
+
class JSONFormatter(logging.Formatter):
|
|
35
|
+
def __init__(self, settings: Settings):
|
|
36
|
+
super().__init__()
|
|
37
|
+
self.__settings = settings
|
|
38
|
+
|
|
39
|
+
def _get_base_ui_url(self) -> str:
|
|
40
|
+
base_url = self.__settings.ui.url
|
|
41
|
+
|
|
42
|
+
# If base URL is not set in settings.yml OR it's set to the URL from within a Scaffold workflow
|
|
43
|
+
# Then return the localhost URL so users can access it.
|
|
44
|
+
if not base_url or base_url == 'http://local.stoobly.com:4200':
|
|
45
|
+
base_url = 'http://localhost:4200'
|
|
46
|
+
return base_url
|
|
47
|
+
|
|
48
|
+
def format(self, record: logging.LogRecord) -> str:
|
|
49
|
+
# Handle delimiter entries such as when changing scenarios
|
|
50
|
+
if hasattr(record, 'delimiter'):
|
|
51
|
+
delimiter_entry = {
|
|
52
|
+
"timestamp": datetime.fromtimestamp(record.created).isoformat(),
|
|
53
|
+
**record.delimiter
|
|
54
|
+
}
|
|
55
|
+
return json.dumps(delimiter_entry)
|
|
56
|
+
|
|
57
|
+
log_entry = {
|
|
58
|
+
"timestamp": datetime.fromtimestamp(record.created).isoformat(),
|
|
59
|
+
"level": record.levelname,
|
|
60
|
+
"message": record.getMessage()
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
# Extract fields from request
|
|
64
|
+
if hasattr(record, 'request') and record.request is not None:
|
|
65
|
+
request: MitmproxyRequest = record.request
|
|
66
|
+
log_entry.update({
|
|
67
|
+
"method": request.method,
|
|
68
|
+
"url": request.pretty_url,
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
# Extract fields from response
|
|
72
|
+
if hasattr(record, 'response') and record.response is not None:
|
|
73
|
+
response = record.response
|
|
74
|
+
log_entry.update({
|
|
75
|
+
"status_code": response.status_code,
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
# Set scenario key
|
|
79
|
+
intercept_settings = InterceptSettings(self.__settings)
|
|
80
|
+
scenario_key = intercept_settings.scenario_key
|
|
81
|
+
log_entry.update({
|
|
82
|
+
"scenario_key": scenario_key
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
# Set scenario name if scenario_key is set
|
|
86
|
+
scenario_name = ""
|
|
87
|
+
if scenario_key:
|
|
88
|
+
scenario_name = InterceptedRequestsLogger._get_scenario_name(scenario_key)
|
|
89
|
+
log_entry.update({
|
|
90
|
+
"scenario_name": scenario_name
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
# Set scaffold service name
|
|
94
|
+
service_name = ""
|
|
95
|
+
env_var_service_name = os.environ.get(SERVICE_NAME_ENV)
|
|
96
|
+
if env_var_service_name:
|
|
97
|
+
service_name = env_var_service_name
|
|
98
|
+
log_entry.update({
|
|
99
|
+
"service_name": service_name
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
# Set scaffold namespace name
|
|
103
|
+
env_var_namespace_name = os.environ.get(WORKFLOW_NAMESPACE_ENV)
|
|
104
|
+
if env_var_namespace_name:
|
|
105
|
+
log_entry.update({
|
|
106
|
+
"namespace": env_var_namespace_name
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
# Set scenario UI URL if scenario_key has a value
|
|
110
|
+
stoobly_ui_scenario_url = ""
|
|
111
|
+
if scenario_key:
|
|
112
|
+
scenario_id = InterceptedRequestsLogger._get_scenario_id(scenario_key)
|
|
113
|
+
if scenario_id:
|
|
114
|
+
base_url = self._get_base_ui_url()
|
|
115
|
+
stoobly_ui_scenario_url = f"{base_url}/agent/scenarios/{scenario_id}"
|
|
116
|
+
log_entry.update({
|
|
117
|
+
"stoobly_ui_scenario_url": stoobly_ui_scenario_url
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
# Set request key and UI URL - prioritize passed-in value, fallback to response headers
|
|
121
|
+
stoobly_ui_request_url = ""
|
|
122
|
+
request_key = None
|
|
123
|
+
|
|
124
|
+
# Check for passed-in request_key first
|
|
125
|
+
if hasattr(record, 'request_key') and record.request_key is not None:
|
|
126
|
+
request_key = record.request_key
|
|
127
|
+
# Fallback to extracting from response headers
|
|
128
|
+
elif hasattr(record, 'response') and record.response is not None:
|
|
129
|
+
request_key = InterceptedRequestsLogger._extract_request_key(record.response)
|
|
130
|
+
|
|
131
|
+
if request_key:
|
|
132
|
+
log_entry.update({
|
|
133
|
+
"request_key": request_key
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
request_id = InterceptedRequestsLogger._get_request_id(request_key)
|
|
137
|
+
if request_id:
|
|
138
|
+
base_url = self._get_base_ui_url()
|
|
139
|
+
stoobly_ui_request_url = f"{base_url}/agent/requests/{request_id}"
|
|
140
|
+
|
|
141
|
+
log_entry.update({
|
|
142
|
+
"stoobly_ui_request_url": stoobly_ui_request_url
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
# Set fixture path if provided
|
|
146
|
+
if hasattr(record, 'fixture_path') and record.fixture_path is not None:
|
|
147
|
+
log_entry.update({
|
|
148
|
+
"fixture_path": record.fixture_path
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
# Set test name if available in request headers
|
|
152
|
+
if hasattr(record, 'request') and record.request is not None:
|
|
153
|
+
request: MitmproxyRequest = record.request
|
|
154
|
+
if hasattr(request, 'headers') and request.headers:
|
|
155
|
+
test_name = request.headers.get(custom_headers.TEST_NAME, "")
|
|
156
|
+
if test_name:
|
|
157
|
+
log_entry.update({
|
|
158
|
+
"test_name": test_name
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
return json.dumps(log_entry)
|
|
162
|
+
|
|
163
|
+
@classmethod
|
|
164
|
+
def set_file_path(cls, file_path: str) -> None:
|
|
165
|
+
cls.__file_path = file_path
|
|
166
|
+
|
|
167
|
+
@classmethod
|
|
168
|
+
def reset_scenario_key(cls) -> None:
|
|
169
|
+
cls.__previous_scenario_key = None
|
|
170
|
+
|
|
171
|
+
@classmethod
|
|
172
|
+
def set_log_level(cls, log_level: str) -> None:
|
|
173
|
+
# Convert string log level to Python standard logging constants
|
|
174
|
+
level_mapping = {
|
|
175
|
+
DEBUG: logging.DEBUG,
|
|
176
|
+
INFO: logging.INFO,
|
|
177
|
+
WARNING: logging.WARNING,
|
|
178
|
+
ERROR: logging.ERROR,
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
numeric_level = level_mapping.get(log_level.lower())
|
|
182
|
+
if numeric_level is None:
|
|
183
|
+
raise ValueError(f"Invalid log level: {log_level}. Must be one of: {', '.join(level_mapping.keys())}")
|
|
184
|
+
cls.__logger.setLevel(numeric_level)
|
|
185
|
+
|
|
186
|
+
@classmethod
|
|
187
|
+
def _get_scenario_name(cls, scenario_key: str) -> str:
|
|
188
|
+
if not scenario_key:
|
|
189
|
+
return None
|
|
190
|
+
|
|
191
|
+
try:
|
|
192
|
+
parsed_key = ScenarioKey(scenario_key)
|
|
193
|
+
scenario = Scenario.find_by(uuid=parsed_key.id)
|
|
194
|
+
return scenario.name if scenario else None
|
|
195
|
+
except (InvalidScenarioKey, Exception):
|
|
196
|
+
return None
|
|
197
|
+
|
|
198
|
+
@classmethod
|
|
199
|
+
def _get_scenario_id(cls, scenario_key: str) -> int:
|
|
200
|
+
if not scenario_key:
|
|
201
|
+
return None
|
|
202
|
+
|
|
203
|
+
try:
|
|
204
|
+
parsed_key = ScenarioKey(scenario_key)
|
|
205
|
+
scenario = Scenario.find_by(uuid=parsed_key.id)
|
|
206
|
+
return scenario.id if scenario else None
|
|
207
|
+
except (InvalidScenarioKey, Exception):
|
|
208
|
+
return None
|
|
209
|
+
|
|
210
|
+
@classmethod
|
|
211
|
+
def _extract_request_key(cls, response) -> str:
|
|
212
|
+
if not response or not hasattr(response, 'headers'):
|
|
213
|
+
return None
|
|
214
|
+
|
|
215
|
+
try:
|
|
216
|
+
return response.headers.get('X-Stoobly-Request-Key')
|
|
217
|
+
except (AttributeError, Exception):
|
|
218
|
+
return None
|
|
219
|
+
|
|
220
|
+
@classmethod
|
|
221
|
+
def _get_request_id(cls, request_key: str) -> int:
|
|
222
|
+
if not request_key:
|
|
223
|
+
return None
|
|
224
|
+
|
|
225
|
+
try:
|
|
226
|
+
parsed_key = RequestKey(request_key)
|
|
227
|
+
request = Request.find_by(uuid=parsed_key.id)
|
|
228
|
+
return request.id if request else None
|
|
229
|
+
except (InvalidRequestKey, Exception):
|
|
230
|
+
return None
|
|
231
|
+
|
|
232
|
+
@classmethod
|
|
233
|
+
def __get_file_path(cls) -> str:
|
|
234
|
+
if cls.__file_path is not None:
|
|
235
|
+
return cls.__file_path
|
|
236
|
+
|
|
237
|
+
data_dir_path = DataDir.instance().path
|
|
238
|
+
return f"{data_dir_path}/tmp/{cls.__NAMESPACE}/logs/requests.json"
|
|
239
|
+
|
|
240
|
+
@classmethod
|
|
241
|
+
def enable_logger_file(cls) -> None:
|
|
242
|
+
cls.__ensure_directory()
|
|
243
|
+
|
|
244
|
+
# Enable the logger before setup so error logging works
|
|
245
|
+
cls.__logger.disabled = False
|
|
246
|
+
|
|
247
|
+
try:
|
|
248
|
+
# Remove all existing handlers to prevent logging to stdout
|
|
249
|
+
cls.__logger.handlers.clear()
|
|
250
|
+
# Prevent propagation to parent loggers which may have console handlers
|
|
251
|
+
cls.__logger.propagate = False
|
|
252
|
+
|
|
253
|
+
file_handler = logging.FileHandler(cls.__get_file_path())
|
|
254
|
+
json_formatter = cls.JSONFormatter(cls.__settings)
|
|
255
|
+
file_handler.setFormatter(json_formatter)
|
|
256
|
+
cls.__logger.addHandler(file_handler)
|
|
257
|
+
|
|
258
|
+
except IOError as e:
|
|
259
|
+
cls.__logger.error(f"Failed to configure logger file output: {e}")
|
|
260
|
+
|
|
261
|
+
@classmethod
|
|
262
|
+
def __log_scenario_change_delimiter(cls, previous_scenario_key: str, current_scenario_key: str) -> None:
|
|
263
|
+
cls.__ensure_directory()
|
|
264
|
+
|
|
265
|
+
previous_name = cls._get_scenario_name(previous_scenario_key)
|
|
266
|
+
current_name = cls._get_scenario_name(current_scenario_key)
|
|
267
|
+
|
|
268
|
+
extra = {
|
|
269
|
+
'delimiter': {
|
|
270
|
+
"type": "----- Scenario change delimiter -----",
|
|
271
|
+
"previous_scenario_key": previous_scenario_key or "",
|
|
272
|
+
"previous_scenario_name": previous_name or "",
|
|
273
|
+
"current_scenario_key": current_scenario_key or "",
|
|
274
|
+
"current_scenario_name": current_name or "",
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
cls.__logger.info(f"Scenario changed to {current_name or ''}", extra=extra)
|
|
279
|
+
|
|
280
|
+
@classmethod
|
|
281
|
+
def __setup_logging(cls, request: MitmproxyRequest = None, response: Response = None, request_key: str = None, fixture_path: str = None) -> dict:
|
|
282
|
+
cls.__ensure_directory()
|
|
283
|
+
cls.__check_scenario_key_changes()
|
|
284
|
+
extra = {}
|
|
285
|
+
|
|
286
|
+
if request is not None:
|
|
287
|
+
extra['request'] = request
|
|
288
|
+
if response is not None:
|
|
289
|
+
extra['response'] = response
|
|
290
|
+
if request_key is not None:
|
|
291
|
+
extra['request_key'] = request_key
|
|
292
|
+
if fixture_path is not None:
|
|
293
|
+
extra['fixture_path'] = fixture_path
|
|
294
|
+
|
|
295
|
+
return extra
|
|
296
|
+
|
|
297
|
+
@classmethod
|
|
298
|
+
def __check_scenario_key_changes(cls) -> None:
|
|
299
|
+
intercept_settings = InterceptSettings(cls.__settings)
|
|
300
|
+
current_scenario_key = intercept_settings.scenario_key
|
|
301
|
+
|
|
302
|
+
if cls.__previous_scenario_key != current_scenario_key:
|
|
303
|
+
cls.__log_scenario_change_delimiter(cls.__previous_scenario_key, current_scenario_key)
|
|
304
|
+
cls.__previous_scenario_key = current_scenario_key
|
|
305
|
+
|
|
306
|
+
@classmethod
|
|
307
|
+
def debug(cls, message: str, *, request: MitmproxyRequest = None, response: Response = None, request_key: str = None, fixture_path: str = None) -> None:
|
|
308
|
+
extra = cls.__setup_logging(request, response, request_key, fixture_path)
|
|
309
|
+
cls.__logger.debug(message, extra=extra if extra else None)
|
|
310
|
+
|
|
311
|
+
@classmethod
|
|
312
|
+
def info(cls, message: str, *, request: MitmproxyRequest = None, response: Response = None, request_key: str = None, fixture_path: str = None) -> None:
|
|
313
|
+
extra = cls.__setup_logging(request, response, request_key, fixture_path)
|
|
314
|
+
cls.__logger.info(message, extra=extra if extra else None)
|
|
315
|
+
|
|
316
|
+
@classmethod
|
|
317
|
+
def warning(cls, message: str, *, request: MitmproxyRequest = None, response: Response = None, request_key: str = None, fixture_path: str = None) -> None:
|
|
318
|
+
extra = cls.__setup_logging(request, response, request_key, fixture_path)
|
|
319
|
+
cls.__logger.warning(message, extra=extra if extra else None)
|
|
320
|
+
|
|
321
|
+
@classmethod
|
|
322
|
+
def error(cls, message: str, *, request: MitmproxyRequest = None, response: Response = None, request_key: str = None, fixture_path: str = None) -> None:
|
|
323
|
+
extra = cls.__setup_logging(request, response, request_key, fixture_path)
|
|
324
|
+
cls.__logger.error(message, extra=extra if extra else None)
|
|
325
|
+
|
|
326
|
+
@classmethod
|
|
327
|
+
def dump_logs(cls):
|
|
328
|
+
file_path = cls.__get_file_path()
|
|
329
|
+
if not os.path.exists(file_path):
|
|
330
|
+
return
|
|
331
|
+
|
|
332
|
+
try:
|
|
333
|
+
with open(file_path, 'r', encoding='utf-8') as f:
|
|
334
|
+
content = f.read()
|
|
335
|
+
if content.strip():
|
|
336
|
+
print(content)
|
|
337
|
+
else:
|
|
338
|
+
print(end='')
|
|
339
|
+
except IOError as e:
|
|
340
|
+
cls.__logger.error(f"Failed to read log file: {e}")
|
|
341
|
+
print(f"Failed to read log file: {e}")
|
|
342
|
+
|
|
343
|
+
@classmethod
|
|
344
|
+
def truncate(cls) -> None:
|
|
345
|
+
cls.__ensure_directory()
|
|
346
|
+
|
|
347
|
+
file_path = cls.__get_file_path()
|
|
348
|
+
|
|
349
|
+
if not os.path.exists(file_path):
|
|
350
|
+
cls.enable_logger_file()
|
|
351
|
+
return
|
|
352
|
+
|
|
353
|
+
try:
|
|
354
|
+
# Close and remove existing handler to release the file lock
|
|
355
|
+
for handler in cls.__logger.handlers[:]:
|
|
356
|
+
if isinstance(handler, logging.FileHandler):
|
|
357
|
+
handler.close()
|
|
358
|
+
cls.__logger.removeHandler(handler)
|
|
359
|
+
|
|
360
|
+
# Now truncate the file
|
|
361
|
+
with open(file_path, 'w', encoding='utf-8') as f:
|
|
362
|
+
f.write('')
|
|
363
|
+
|
|
364
|
+
# Re-enable logging with a fresh handler
|
|
365
|
+
cls.enable_logger_file()
|
|
366
|
+
cls.__logger.debug(f"Cleared log file: {file_path}")
|
|
367
|
+
|
|
368
|
+
cls.reset_scenario_key()
|
|
369
|
+
except IOError as e:
|
|
370
|
+
cls.__logger.error(f"Failed to clear log file: {e}")
|
|
371
|
+
|
|
372
|
+
@classmethod
|
|
373
|
+
def __ensure_directory(cls):
|
|
374
|
+
file_path = cls.__get_file_path()
|
|
375
|
+
directory = os.path.dirname(file_path)
|
|
376
|
+
|
|
377
|
+
if directory and not os.path.exists(directory):
|
|
378
|
+
try:
|
|
379
|
+
cls.__logger.debug(f"created missing directory: {directory}")
|
|
380
|
+
os.makedirs(directory, exist_ok=True)
|
|
381
|
+
except OSError as e:
|
|
382
|
+
cls.__logger.error(f"Failed to create log directory: {e}")
|
|
@@ -109,11 +109,11 @@ class TestScaffoldE2e():
|
|
|
109
109
|
def create_scaffold_setup(self, runner, app_dir_path, app_name, target_workflow_name, external_service_docker_compose, external_https_service_docker_compose, local_service_docker_compose, local_service_mock_docker_compose_path):
|
|
110
110
|
ScaffoldCliInvoker.cli_app_create(runner, app_dir_path, app_name)
|
|
111
111
|
|
|
112
|
-
# Create
|
|
112
|
+
# Create user defined services
|
|
113
113
|
ScaffoldCliInvoker.cli_service_create(runner, app_dir_path, external_service_docker_compose.hostname, external_service_docker_compose.service_name, False)
|
|
114
114
|
ScaffoldCliInvoker.cli_service_create(runner, app_dir_path, external_https_service_docker_compose.hostname, external_https_service_docker_compose.service_name, True)
|
|
115
115
|
|
|
116
|
-
# Create
|
|
116
|
+
# Create user defined custom container services
|
|
117
117
|
ScaffoldCliInvoker.cli_service_create(runner, app_dir_path, local_service_docker_compose.hostname, local_service_docker_compose.service_name, False)
|
|
118
118
|
|
|
119
119
|
# Validate docker-compose path exists
|
|
@@ -1 +1 @@
|
|
|
1
|
-
1.10.2
|
|
1
|
+
1.10.2
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
stoobly_agent/__init__.py,sha256=
|
|
1
|
+
stoobly_agent/__init__.py,sha256=K4x_RYNZ1yt5BujLPBK0239IWwUcGdZ8CwmPVeTMXuY,45
|
|
2
2
|
stoobly_agent/__main__.py,sha256=tefOkFZeCFU4l3C-Y4R_lR9Yt-FETISiXGUnbh6Os54,146
|
|
3
3
|
stoobly_agent/app/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
4
|
stoobly_agent/app/api/__init__.py,sha256=NIgcbX7iiWrApsCITXlmhr4SYbWS0fwb01x-F3jTFdo,666
|
|
@@ -61,11 +61,11 @@ stoobly_agent/app/cli/helpers/trace_aliases.py,sha256=8N8pnsnONwVv-fabCTvDlXec-W
|
|
|
61
61
|
stoobly_agent/app/cli/helpers/trace_context_facade.py,sha256=3MDjY_bdhvE2dad_B_w9gemCZZoiVjotbE8o9hrmVYo,1027
|
|
62
62
|
stoobly_agent/app/cli/helpers/validations.py,sha256=o9_DqlAIw98QcMNXGsAnELQ6DeXdSRa1TLiTZu3V7ns,6236
|
|
63
63
|
stoobly_agent/app/cli/helpers/verify_raw_request_service.py,sha256=tmLeRBYhNgAQeJD-rF6Nj22F_TfVktVSb1wjRPFblBQ,658
|
|
64
|
-
stoobly_agent/app/cli/intercept_cli.py,sha256=
|
|
64
|
+
stoobly_agent/app/cli/intercept_cli.py,sha256=ULJPgN21qNBhO97O13HtQplXYlAz1HEOg7y9Y3RnrcM,6698
|
|
65
65
|
stoobly_agent/app/cli/main_group.py,sha256=3UzBxin2f2p5KNqo6Oayo_I1VI2pHpqOoRexUnJ74a4,2187
|
|
66
66
|
stoobly_agent/app/cli/project_cli.py,sha256=EXjeLjbnq9PhfCjvyfZ0UnJ2tejeCS0SIAo3Nc4fKOc,3852
|
|
67
67
|
stoobly_agent/app/cli/report_cli.py,sha256=ZxJw0Xkx7KFZJn9e45BSKRKon8AD0Msrwy1fbPfbv0c,2543
|
|
68
|
-
stoobly_agent/app/cli/request_cli.py,sha256=
|
|
68
|
+
stoobly_agent/app/cli/request_cli.py,sha256=TEZNAKqDaZClNEJsRq1UcYrH60s0E20OPGfxXDdFOJ4,8448
|
|
69
69
|
stoobly_agent/app/cli/scaffold/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
70
70
|
stoobly_agent/app/cli/scaffold/app.py,sha256=7d5nHnQwyBxEwSC2Jrvbk2U3dj9OnhaHrE2zmC37oOs,3922
|
|
71
71
|
stoobly_agent/app/cli/scaffold/app_command.py,sha256=x--ejtVSBL0Jz8OiakBtxfI2IZFAWJWCwuSJo7TEU9Y,2386
|
|
@@ -81,29 +81,29 @@ stoobly_agent/app/cli/scaffold/docker/builder.py,sha256=uiGqhxBHEasZAqLzjKUGUs-1
|
|
|
81
81
|
stoobly_agent/app/cli/scaffold/docker/constants.py,sha256=itm2owGfW53yDrfgkKQ4HIBdiwRC0IPOUTwUT9P78Lw,837
|
|
82
82
|
stoobly_agent/app/cli/scaffold/docker/service/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
83
83
|
stoobly_agent/app/cli/scaffold/docker/service/build_decorator.py,sha256=fGWtt_TFOaIVirlkUWVKVEhDPmn0A4D53mNSud-qzHg,1019
|
|
84
|
-
stoobly_agent/app/cli/scaffold/docker/service/builder.py,sha256=
|
|
84
|
+
stoobly_agent/app/cli/scaffold/docker/service/builder.py,sha256=_cTBKTv0X-b1mAEA9GItLvPp8pTHvymgHyNmw04t0V4,6140
|
|
85
85
|
stoobly_agent/app/cli/scaffold/docker/service/configure_gateway.py,sha256=Wsyd2Mche2greULTFYWm-SIhenh-I5hlauSOdGG2urU,4011
|
|
86
86
|
stoobly_agent/app/cli/scaffold/docker/service/types.py,sha256=qB-yYHlu-PZDc0HYgTUvE5bWNpHxaSThC3JUG8okR1k,88
|
|
87
87
|
stoobly_agent/app/cli/scaffold/docker/template_files.py,sha256=oqcocCAyXRJ8aq8M1zVLUUrxU3VU6D6V7ibZUYTr2b4,4724
|
|
88
88
|
stoobly_agent/app/cli/scaffold/docker/workflow/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
89
89
|
stoobly_agent/app/cli/scaffold/docker/workflow/build_decorator.py,sha256=K9yNlOc_r0vvXZ5BZDwk5IMpzgrx6v-igsTs_8oae6s,720
|
|
90
|
-
stoobly_agent/app/cli/scaffold/docker/workflow/builder.py,sha256=
|
|
90
|
+
stoobly_agent/app/cli/scaffold/docker/workflow/builder.py,sha256=22uKov9GJ5PqM8bEpKhiqqLexlHIrrAhNphWi104pSc,4396
|
|
91
91
|
stoobly_agent/app/cli/scaffold/docker/workflow/command_decorator.py,sha256=6gVdtTKIOyDlAfzOWefoffp6CtOC7gHz4dUQ6Z9lxWE,874
|
|
92
92
|
stoobly_agent/app/cli/scaffold/docker/workflow/decorators_factory.py,sha256=BkxPzj__rBQa4_WXkfSV71-T9LayFzCOgCLyuJA4s-Q,1174
|
|
93
93
|
stoobly_agent/app/cli/scaffold/docker/workflow/detached_decorator.py,sha256=qrvIYeCgnQNAs4n_g4DfTgO5fl9nW0e6wOIMhFELqXw,1178
|
|
94
94
|
stoobly_agent/app/cli/scaffold/docker/workflow/dns_decorator.py,sha256=DGaSlbOvAAa16buFwQnkGHyqht4VR3rnIBOWr-ImIf8,952
|
|
95
95
|
stoobly_agent/app/cli/scaffold/docker/workflow/local_decorator.py,sha256=xp1TmP8drOyl9Zhm5B1ci6NqPqRFDr2yxipmvSljgiE,717
|
|
96
|
-
stoobly_agent/app/cli/scaffold/docker/workflow/mock_decorator.py,sha256=
|
|
96
|
+
stoobly_agent/app/cli/scaffold/docker/workflow/mock_decorator.py,sha256=CJyO_0Oo3pUrafqmdqLl5bJIj35zKohl8PVGgokRfW4,1245
|
|
97
97
|
stoobly_agent/app/cli/scaffold/docker/workflow/reverse_proxy_decorator.py,sha256=tSPnscsBZusBaSt_NlB4exrZ2MnWMRAUJgw_NaEdHiw,1199
|
|
98
|
-
stoobly_agent/app/cli/scaffold/docker/workflow/run_command.py,sha256=
|
|
98
|
+
stoobly_agent/app/cli/scaffold/docker/workflow/run_command.py,sha256=LLiC7KCgo9ZlmXcdaidmcN449GuDK_8dYykCNFjfhyo,18217
|
|
99
99
|
stoobly_agent/app/cli/scaffold/env.py,sha256=dT33tHoQaUxfsFCYm8kfaAv-qPVrUPmNFQmLnFQhZeQ,1107
|
|
100
100
|
stoobly_agent/app/cli/scaffold/hosts_file_manager.py,sha256=zNX5wh6zXQ4J2BA0YYdD7_CPqDz02b_ghXsY3oTjjB4,4999
|
|
101
101
|
stoobly_agent/app/cli/scaffold/local/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
102
102
|
stoobly_agent/app/cli/scaffold/local/service/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
103
|
-
stoobly_agent/app/cli/scaffold/local/service/builder.py,sha256=
|
|
103
|
+
stoobly_agent/app/cli/scaffold/local/service/builder.py,sha256=9awizOv_LYAWgEXVguQOLZhO8gGEa_X_x1MRsQJEioU,1522
|
|
104
104
|
stoobly_agent/app/cli/scaffold/local/workflow/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
105
|
-
stoobly_agent/app/cli/scaffold/local/workflow/builder.py,sha256=
|
|
106
|
-
stoobly_agent/app/cli/scaffold/local/workflow/run_command.py,sha256=
|
|
105
|
+
stoobly_agent/app/cli/scaffold/local/workflow/builder.py,sha256=EghacafZjr06igvQtWTjt9vwILHtJxSl3rPEW-VD6iU,828
|
|
106
|
+
stoobly_agent/app/cli/scaffold/local/workflow/run_command.py,sha256=7XhI6X-TKAGv-HuOvh6Knm7R5DDpfi8ZZ8BJnHQwGtY,15956
|
|
107
107
|
stoobly_agent/app/cli/scaffold/managed_services_docker_compose.py,sha256=-wLBXUi7DCWsfm5KzZzd_kdJKOTl1NT924XR7dyjbSY,574
|
|
108
108
|
stoobly_agent/app/cli/scaffold/service.py,sha256=74JwjTRRkk6lo-k9hre1iGztbKa9zDqjPVx3Qgpze-s,699
|
|
109
109
|
stoobly_agent/app/cli/scaffold/service_command.py,sha256=j-lkG5Zth_CBHa6Z9Kv3dJwxX9gylFBZMZbW691R8ZU,1480
|
|
@@ -114,11 +114,11 @@ stoobly_agent/app/cli/scaffold/service_dependency.py,sha256=olr_s_cfn51Pz5FlIihl
|
|
|
114
114
|
stoobly_agent/app/cli/scaffold/service_docker_compose.py,sha256=fVUZ-oo-bn5GVZp8JgGq7AkiQQ6-JkxwK_OMlinS9WM,915
|
|
115
115
|
stoobly_agent/app/cli/scaffold/service_update_command.py,sha256=oWusBKfvjt4RnK03_V3CJYWrfsCI4_LcR7W12eLXMR4,2579
|
|
116
116
|
stoobly_agent/app/cli/scaffold/service_workflow.py,sha256=sQ_Edy_wGHKMXpD0DmhnOWkGEKz7gSgEGNI8f7aXOdg,444
|
|
117
|
-
stoobly_agent/app/cli/scaffold/service_workflow_validate_command.py,sha256=
|
|
117
|
+
stoobly_agent/app/cli/scaffold/service_workflow_validate_command.py,sha256=uV5HcgWZu1RubPImVDyIUlZUNTok4Qr6CueQQswBoF8,11788
|
|
118
118
|
stoobly_agent/app/cli/scaffold/templates/__init__.py,sha256=x8C_a0VoO_vUbosp4_6IC1U7Ge9NnUdVKDPpVMtMkeY,171
|
|
119
|
-
stoobly_agent/app/cli/scaffold/templates/app/.Dockerfile.context,sha256=
|
|
120
|
-
stoobly_agent/app/cli/scaffold/templates/app/.Makefile,sha256=
|
|
121
|
-
stoobly_agent/app/cli/scaffold/templates/app/.docker-compose.base.yml,sha256=
|
|
119
|
+
stoobly_agent/app/cli/scaffold/templates/app/.Dockerfile.context,sha256=tQgVLNA2SWy1K_dl-loPKau55F3nyPp4vhCppol-fiQ,160
|
|
120
|
+
stoobly_agent/app/cli/scaffold/templates/app/.Makefile,sha256=cywqyJkxHDRr3JOQSwx0pFwSi-b_iw6GzHxeIoVP66k,10028
|
|
121
|
+
stoobly_agent/app/cli/scaffold/templates/app/.docker-compose.base.yml,sha256=cs6bRu6p8djhTKRic9UFTZujlNpHGudR2SrKauntmHg,381
|
|
122
122
|
stoobly_agent/app/cli/scaffold/templates/app/.docker-compose.networks.yml,sha256=I4PbJpQjFHb5IbAUWNvYM6okDEtmwtKFDQg-yog05WM,141
|
|
123
123
|
stoobly_agent/app/cli/scaffold/templates/app/Makefile,sha256=TEmPG7Bf0KZOnmfsgdzza3UdwcVMmM5Lj1YdLc4cgjA,79
|
|
124
124
|
stoobly_agent/app/cli/scaffold/templates/app/build/.config.yml,sha256=8Wt8ZZ5irvBYYS44xGrR_EWlZDuXH9kyWmquzsh7s8g,19
|
|
@@ -160,8 +160,8 @@ stoobly_agent/app/cli/scaffold/templates/app/gateway/test/.docker-compose.yml,sh
|
|
|
160
160
|
stoobly_agent/app/cli/scaffold/templates/app/stoobly-ui/.config.yml,sha256=XnLQZMzzMMIwVycjyPN5QXsmRztkTFAna1kIHYuDfJQ,19
|
|
161
161
|
stoobly_agent/app/cli/scaffold/templates/app/stoobly-ui/.docker-compose.base.yml,sha256=W6cUnaJ6dmq4VOgrue_YaW4TyFx9zd4YrrRsxOimPEA,140
|
|
162
162
|
stoobly_agent/app/cli/scaffold/templates/app/stoobly-ui/exec/.docker-compose.yml,sha256=_lku3K0t2TRLMOcoSKcisCLQO_YCXZoevr_q6anjhL8,327
|
|
163
|
-
stoobly_agent/app/cli/scaffold/templates/app/stoobly-ui/mock/.docker-compose.yml,sha256=
|
|
164
|
-
stoobly_agent/app/cli/scaffold/templates/app/stoobly-ui/record/.docker-compose.yml,sha256=
|
|
163
|
+
stoobly_agent/app/cli/scaffold/templates/app/stoobly-ui/mock/.docker-compose.yml,sha256=e4Bjb0deT6afbjHWG9aPLj83uJrcgeZq0xR0gOpPMBE,342
|
|
164
|
+
stoobly_agent/app/cli/scaffold/templates/app/stoobly-ui/record/.docker-compose.yml,sha256=e4Bjb0deT6afbjHWG9aPLj83uJrcgeZq0xR0gOpPMBE,342
|
|
165
165
|
stoobly_agent/app/cli/scaffold/templates/build/services/build/mock/.configure,sha256=B9re5KYSNfvbcawVQnU5JX-RdAWEjWP3327L29dTEK0,392
|
|
166
166
|
stoobly_agent/app/cli/scaffold/templates/build/services/build/mock/.init,sha256=wyh47rAOxaqOa_O-t0kaqZE6aAc0h5bsX4t-PSkVPJM,302
|
|
167
167
|
stoobly_agent/app/cli/scaffold/templates/build/services/build/mock/.run,sha256=0ib4oiLYiU-27nwhHFCNzQXz4fsAxypYECoAhZhkVUI,239
|
|
@@ -182,6 +182,7 @@ stoobly_agent/app/cli/scaffold/templates/build/services/entrypoint/test/.init,sh
|
|
|
182
182
|
stoobly_agent/app/cli/scaffold/templates/build/services/entrypoint/test/.run,sha256=LQs_bJiN3LaC0eJnaF_Fnrr9ZIlxkaM8tnWMxjCovXM,446
|
|
183
183
|
stoobly_agent/app/cli/scaffold/templates/build/workflows/exec/intercept/.disable,sha256=xVf4Pk1RLvJm7Ff0rbGoWhYHPv0ME5e93fxS2yFqLnE,45
|
|
184
184
|
stoobly_agent/app/cli/scaffold/templates/build/workflows/exec/intercept/.enable,sha256=sfUSPG4uHdXX95BLgivXQYLbsLBP2DjJIiSTXRtvXuY,188
|
|
185
|
+
stoobly_agent/app/cli/scaffold/templates/build/workflows/exec/request/log/.list,sha256=JyfIkHOJbkjkJMibFVdRNDGtgSMRNE1PEHJEOZMY7tE,88
|
|
185
186
|
stoobly_agent/app/cli/scaffold/templates/build/workflows/exec/scaffold/.down,sha256=Eb-DY3NYLBny2Akx4R3ckqHqi_5gzYKNp9tJgFbeFOQ,205
|
|
186
187
|
stoobly_agent/app/cli/scaffold/templates/build/workflows/exec/scaffold/.logs,sha256=ebsSW7N3RbT4asqDVpdKWzztC3FZxXLpjnxsiblMSGQ,184
|
|
187
188
|
stoobly_agent/app/cli/scaffold/templates/build/workflows/exec/scaffold/.mkcert,sha256=vyHaXmvy-7oL2RD8rIxwT-fdJS5kXmB0yHK5fRMo_cM,46
|
|
@@ -240,7 +241,7 @@ stoobly_agent/app/cli/scaffold/workflow_create_command.py,sha256=5gSkxNxrXLeobLU
|
|
|
240
241
|
stoobly_agent/app/cli/scaffold/workflow_env.py,sha256=shPjoX1SWe7K6pGpZvw2fPVHWd6j_azTe58jvOjGUns,607
|
|
241
242
|
stoobly_agent/app/cli/scaffold/workflow_log_command.py,sha256=Bke4lMOMxuDUFuAx9nlXHbKgYMO4KAg9ASHvjz4aVWc,1372
|
|
242
243
|
stoobly_agent/app/cli/scaffold/workflow_namesapce.py,sha256=VNaZrcqMMeqrzpPGhD9-oaZems1k0ebRc6wR74EvA8c,1170
|
|
243
|
-
stoobly_agent/app/cli/scaffold/workflow_run_command.py,sha256=
|
|
244
|
+
stoobly_agent/app/cli/scaffold/workflow_run_command.py,sha256=IPKQyKZ5zZ1fBn5JyT5dEZxCLzW7gFbkLywWEmArBMg,6248
|
|
244
245
|
stoobly_agent/app/cli/scaffold/workflow_validate_command.py,sha256=Uo_yo6rVR1ZR7xpvsQvlH48AyMBVLRupd4G-bRjzm_Q,5584
|
|
245
246
|
stoobly_agent/app/cli/scaffold_cli.py,sha256=YnRKrC_XQ5OdQ1tgA-oL4rmamw70DlMm17SqIAQ9sfU,33677
|
|
246
247
|
stoobly_agent/app/cli/scenario_cli.py,sha256=lA9a4UNnLzrbJX5JJE07KGb5i9pBd2c2vdFUW6_7k0E,8345
|
|
@@ -350,7 +351,7 @@ stoobly_agent/app/proxy/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG
|
|
|
350
351
|
stoobly_agent/app/proxy/constants/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
351
352
|
stoobly_agent/app/proxy/constants/custom_response_codes.py,sha256=1CaApt_6W7GrxvN8_Ozbf_SEodVEQaNZRR2sMYpI0U8,40
|
|
352
353
|
stoobly_agent/app/proxy/context.py,sha256=m6iu6QSZsim8meZS8H8DsZeXyjfoC-MRMHhQD1MFKM0,532
|
|
353
|
-
stoobly_agent/app/proxy/handle_mock_service.py,sha256=
|
|
354
|
+
stoobly_agent/app/proxy/handle_mock_service.py,sha256=t05JS8LTWWbAh06hQ_4C1gi2TzdF6VIQmckuTD6-QAc,9936
|
|
354
355
|
stoobly_agent/app/proxy/handle_record_service.py,sha256=TXIxT5zYOCd96LoINvpCPm2PSnwCAPQQLqfjch9iVlo,4516
|
|
355
356
|
stoobly_agent/app/proxy/handle_replay_service.py,sha256=6gB3y6xT6WEXduxFgK5bd3xiH2utrsfDSBV4i2Ai34Y,3085
|
|
356
357
|
stoobly_agent/app/proxy/handle_test_service.py,sha256=WkMPbM4argVtl-TQB7VdQIvB8cOwURAahxFX5Vkqwws,8405
|
|
@@ -396,7 +397,7 @@ stoobly_agent/app/proxy/replay/replay_request_service.py,sha256=pRsKx3d7caXiHrEH
|
|
|
396
397
|
stoobly_agent/app/proxy/replay/replay_scenario_service.py,sha256=9jV-iO5EBg8geUblEtjjWRFIkom_Pqmo7P-lTc3S4Xw,2824
|
|
397
398
|
stoobly_agent/app/proxy/replay/rewrite_params_service.py,sha256=jEHlT6_OHq_VBa09Hd6QaRyErv7tZnziDvW7m3Q8CQg,2234
|
|
398
399
|
stoobly_agent/app/proxy/replay/trace_context.py,sha256=lKpnQWVCUTcMPE-SOi90za3U1yL0VeBlIgj_KP63VsE,10435
|
|
399
|
-
stoobly_agent/app/proxy/run.py,sha256=
|
|
400
|
+
stoobly_agent/app/proxy/run.py,sha256=viThhDVG0WR5r0xMTU6-9zgJW08S6WEAAMn_f5t77tw,4093
|
|
400
401
|
stoobly_agent/app/proxy/settings.py,sha256=R0LkSa9HrkUXvCd-nur4syJePjbQZdlnAnOPpGnCx38,2172
|
|
401
402
|
stoobly_agent/app/proxy/simulate_intercept_service.py,sha256=R-L2dh2dfYFebttWXU0NwyxFI_jP6Ud36oKPC-T8HiI,1942
|
|
402
403
|
stoobly_agent/app/proxy/test/__init__.py,sha256=uW0Ab27oyH2odTeVRjcuUJF8A1FLbTT5sBMzhGZr1so,89
|
|
@@ -458,11 +459,11 @@ stoobly_agent/app/settings/types/remote_settings.py,sha256=4PvEGKULXM0zv29XTDzV7
|
|
|
458
459
|
stoobly_agent/app/settings/types/ui_settings.py,sha256=BqPy2F32AbODqzi2mp2kRk28QVUydQIwVmvftn46pco,84
|
|
459
460
|
stoobly_agent/app/settings/ui_settings.py,sha256=D4sP26dEhOlGPdeQgm4GyAYN3ENWXkfZ9s0ETlt3QEI,1175
|
|
460
461
|
stoobly_agent/app/settings/url_rule.py,sha256=Qx7YrIpVRSC-4LeNiCAfCtE50Jou4423hojMW-4qUYg,954
|
|
461
|
-
stoobly_agent/cli.py,sha256=
|
|
462
|
+
stoobly_agent/cli.py,sha256=Lre8Nu99cjhwOh0znXUjKFB1LSI1bV7aKghJOe273F0,14406
|
|
462
463
|
stoobly_agent/config/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
463
464
|
stoobly_agent/config/constants/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
464
465
|
stoobly_agent/config/constants/alias_resolve_strategy.py,sha256=_R1tVqFnyGxCraVS5-dhSskaDj_X8-NthsY7i_bEt9M,119
|
|
465
|
-
stoobly_agent/config/constants/custom_headers.py,sha256=
|
|
466
|
+
stoobly_agent/config/constants/custom_headers.py,sha256=NDuZ25zdtgObs_xoZ9BBXXwrFGCr6akyWJPnIZcSNus,1478
|
|
466
467
|
stoobly_agent/config/constants/env_vars.py,sha256=pS6U1RHFljamBfoVyPiroH3SVy4-sTeESo6p5n19yls,1408
|
|
467
468
|
stoobly_agent/config/constants/headers.py,sha256=Hfv7R8_NPXAGaMiZPqywGZDnr0qcVUyfenPb4g465rE,169
|
|
468
469
|
stoobly_agent/config/constants/intercept_policy.py,sha256=5hIgOft8PQmCRdOHb5OEvEj10tU66DIQF3GYltlWyM8,25
|
|
@@ -546,6 +547,7 @@ stoobly_agent/lib/api/test_responses_resource.py,sha256=RdNj7N6xC7jW5zUoKfgNg8Ne
|
|
|
546
547
|
stoobly_agent/lib/api/tests_resource.py,sha256=EKyI0xTgzRSPyGdepgteVH3EHUkXIzrt3HXahBau6CY,855
|
|
547
548
|
stoobly_agent/lib/api/users_resource.py,sha256=xR0ug5xifuvRw4jdF4cVjrWBdkFccmL9_fy8ZxOPX74,477
|
|
548
549
|
stoobly_agent/lib/cache.py,sha256=7sYq2wh6I5LmtKqvJYoY89h_PvZ21zvS9pCBCEutuCM,2266
|
|
550
|
+
stoobly_agent/lib/intercepted_requests_logger.py,sha256=82ZfwV27NZGIWRArL7Oqv9LX66k2I-MqVcOccDrOdVU,14923
|
|
549
551
|
stoobly_agent/lib/logger.py,sha256=6s_8UxXLJt1jor-Ez0xCBWhuAD932sTEBQBnNXpFTtQ,1971
|
|
550
552
|
stoobly_agent/lib/orm/__init__.py,sha256=UCAL40_L9qf1sUqvUWKzc551jKLR4HKqRNLPdOuXl2w,1199
|
|
551
553
|
stoobly_agent/lib/orm/base.py,sha256=3PZx0OWK241vrr8pLLQL0GFhKxvYVDFyVMMdYzhxMPM,358
|
|
@@ -723,7 +725,7 @@ stoobly_agent/test/app/cli/request/request_snapshot_test.py,sha256=3kMmv0CuvnMXL
|
|
|
723
725
|
stoobly_agent/test/app/cli/request/request_test_test.py,sha256=-cJNXKjgryVVfVt-7IN5fIhBwe3NjFoPmeavDH8lAjU,5527
|
|
724
726
|
stoobly_agent/test/app/cli/scaffold/docker/cli_invoker.py,sha256=W4ZZgrgUU0hBm_l0x04eR_9eGVyfbLSMbDVGqhpi2qE,5391
|
|
725
727
|
stoobly_agent/test/app/cli/scaffold/docker/cli_test.py,sha256=wkYnRQo8OBDds-pGmdc2cYs5yxgO_2GiDxzI1imGntc,4301
|
|
726
|
-
stoobly_agent/test/app/cli/scaffold/docker/e2e_test.py,sha256=
|
|
728
|
+
stoobly_agent/test/app/cli/scaffold/docker/e2e_test.py,sha256=cdsXrN1YK4s2mugGWC2NCFOsgcebaPugEEUh6rxsP1w,13659
|
|
727
729
|
stoobly_agent/test/app/cli/scaffold/hosts_file_manager_test.py,sha256=ztcPh1x0ZCW1FWA5YL4ulEVjfbW9TOPgk1bnSDPNmCw,2287
|
|
728
730
|
stoobly_agent/test/app/cli/scaffold/local/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
729
731
|
stoobly_agent/test/app/cli/scaffold/local/cli_invoker.py,sha256=YLQuz5JjvzfqFKk-BFT-Bl9EZgj37ZDZtwNsvqNRDHA,4138
|
|
@@ -755,7 +757,7 @@ stoobly_agent/test/app/models/factories/resource/local_db/helpers/log_test.py,sh
|
|
|
755
757
|
stoobly_agent/test/app/models/factories/resource/local_db/helpers/tiebreak_scenario_request_test.py,sha256=a1SFLyEyRRLuADvAw6ckQQKORFXvyK1lyrbkaLWx8oU,3399
|
|
756
758
|
stoobly_agent/test/app/models/factories/resource/local_db/request_adapter_test.py,sha256=Pzq1cBPnP9oSWG-p0c-VoymoHxgp483QmNwmV1b78RA,8453
|
|
757
759
|
stoobly_agent/test/app/models/factories/resource/local_db/response_adapter_test.py,sha256=9P95EKH5rZGOrmRkRIDlQZqtiLJHk9735og18Ffwpfw,2204
|
|
758
|
-
stoobly_agent/test/app/models/schemas/.stoobly/db/VERSION,sha256=
|
|
760
|
+
stoobly_agent/test/app/models/schemas/.stoobly/db/VERSION,sha256=UAIPUXjU5C_KBbJFX74l8Kihm3U3urn6h0KexeviDeE,7
|
|
759
761
|
stoobly_agent/test/app/models/schemas/.stoobly/db/stoobly_agent.sqlite3,sha256=ch8gNx6zIelLKQx65gwFx_LRNqUD3EC5xcHZ0ukIQiU,188416
|
|
760
762
|
stoobly_agent/test/app/models/schemas/.stoobly/settings.yml,sha256=vLwMjweKOdod6tSLtIlyBefPQuNXq9wio4kBaODKtAU,726
|
|
761
763
|
stoobly_agent/test/app/models/schemas/.stoobly/tmp/options.json,sha256=OTRzarwus48CTrItedXCrgQttJHSEZonEYc7R_knvYg,2212
|
|
@@ -797,8 +799,8 @@ stoobly_agent/test/mock_data/scaffold/docker-compose-local-service.yml,sha256=1W
|
|
|
797
799
|
stoobly_agent/test/mock_data/scaffold/index.html,sha256=qJwuYajKZ4ihWZrJQ3BNObV5kf1VGnnm_vqlPJzdqLE,258
|
|
798
800
|
stoobly_agent/test/mock_data/uspto.yaml,sha256=6U5se7C3o-86J4m9xpOk9Npias399f5CbfWzR87WKwE,7835
|
|
799
801
|
stoobly_agent/test/test_helper.py,sha256=6v4AHeqYPw7vtRoxET_ubmRWPJoSmTR_DVHay3FxNbQ,1299
|
|
800
|
-
stoobly_agent-1.
|
|
801
|
-
stoobly_agent-1.
|
|
802
|
-
stoobly_agent-1.
|
|
803
|
-
stoobly_agent-1.
|
|
804
|
-
stoobly_agent-1.
|
|
802
|
+
stoobly_agent-1.11.0.dist-info/METADATA,sha256=MMcUv_P6qdNNHfgupNdDP4MYHuqfN7rth4YkbS_c1gc,3203
|
|
803
|
+
stoobly_agent-1.11.0.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
|
|
804
|
+
stoobly_agent-1.11.0.dist-info/entry_points.txt,sha256=aq5wix5oC8MDQtmyPGU0xaFrsjJg7WH28NmXh2sc3Z8,56
|
|
805
|
+
stoobly_agent-1.11.0.dist-info/licenses/LICENSE,sha256=o93sj12cdoEOsTCjPaPFsw3Xq0SXs3pPcY-9reE2sEw,548
|
|
806
|
+
stoobly_agent-1.11.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|