stoobly-agent 1.9.12__py3-none-any.whl → 1.10.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- stoobly_agent/__init__.py +1 -1
- stoobly_agent/app/api/__init__.py +4 -20
- stoobly_agent/app/api/application_http_request_handler.py +5 -2
- stoobly_agent/app/api/configs_controller.py +3 -3
- stoobly_agent/app/cli/decorators/exec.py +1 -1
- stoobly_agent/app/cli/helpers/handle_config_update_service.py +4 -0
- stoobly_agent/app/cli/intercept_cli.py +40 -7
- stoobly_agent/app/cli/scaffold/app_command.py +4 -0
- stoobly_agent/app/cli/scaffold/app_config.py +21 -3
- stoobly_agent/app/cli/scaffold/app_create_command.py +109 -2
- stoobly_agent/app/cli/scaffold/constants.py +14 -3
- stoobly_agent/app/cli/scaffold/docker/constants.py +4 -6
- stoobly_agent/app/cli/scaffold/docker/service/build_decorator.py +2 -2
- stoobly_agent/app/cli/scaffold/docker/service/builder.py +36 -10
- stoobly_agent/app/cli/scaffold/docker/workflow/builder.py +0 -27
- stoobly_agent/app/cli/scaffold/docker/workflow/command_decorator.py +25 -0
- stoobly_agent/app/cli/scaffold/docker/workflow/decorators_factory.py +7 -2
- stoobly_agent/app/cli/scaffold/docker/workflow/detached_decorator.py +42 -0
- stoobly_agent/app/cli/scaffold/docker/workflow/local_decorator.py +26 -0
- stoobly_agent/app/cli/scaffold/docker/workflow/mock_decorator.py +9 -10
- stoobly_agent/app/cli/scaffold/docker/workflow/reverse_proxy_decorator.py +5 -8
- stoobly_agent/app/cli/scaffold/service_config.py +133 -34
- stoobly_agent/app/cli/scaffold/service_create_command.py +11 -2
- stoobly_agent/app/cli/scaffold/service_dependency.py +51 -0
- stoobly_agent/app/cli/scaffold/service_docker_compose.py +3 -3
- stoobly_agent/app/cli/scaffold/service_workflow_validate_command.py +10 -7
- stoobly_agent/app/cli/scaffold/templates/app/.Dockerfile.context +1 -1
- stoobly_agent/app/cli/scaffold/templates/app/build/.docker-compose.base.yml +2 -2
- stoobly_agent/app/cli/scaffold/templates/app/build/mock/bin/configure +1 -1
- stoobly_agent/app/cli/scaffold/templates/app/build/mock/docker-compose.yml +16 -6
- stoobly_agent/app/cli/scaffold/templates/app/build/record/bin/configure +26 -1
- stoobly_agent/app/cli/scaffold/templates/app/build/record/docker-compose.yml +16 -6
- stoobly_agent/app/cli/scaffold/templates/app/build/test/bin/configure +1 -1
- stoobly_agent/app/cli/scaffold/templates/app/build/test/docker-compose.yml +16 -6
- stoobly_agent/app/cli/scaffold/templates/app/entrypoint/.docker-compose.base.yml +2 -2
- stoobly_agent/app/cli/scaffold/templates/app/entrypoint/mock/bin/configure +1 -1
- stoobly_agent/app/cli/scaffold/templates/app/entrypoint/mock/docker-compose.yml +16 -10
- stoobly_agent/app/cli/scaffold/templates/app/entrypoint/record/bin/configure +1 -1
- stoobly_agent/app/cli/scaffold/templates/app/entrypoint/record/docker-compose.yml +16 -10
- stoobly_agent/app/cli/scaffold/templates/app/entrypoint/test/bin/configure +1 -1
- stoobly_agent/app/cli/scaffold/templates/app/entrypoint/test/docker-compose.yml +16 -10
- stoobly_agent/app/cli/scaffold/templates/app/stoobly-ui/.docker-compose.base.yml +2 -1
- stoobly_agent/app/cli/scaffold/templates/app/stoobly-ui/mock/.docker-compose.mock.yml +6 -3
- stoobly_agent/app/cli/scaffold/templates/app/stoobly-ui/record/.docker-compose.record.yml +6 -4
- stoobly_agent/app/cli/scaffold/templates/build/workflows/record/.configure +21 -1
- stoobly_agent/app/cli/scaffold/templates/constants.py +4 -0
- stoobly_agent/app/cli/scaffold/templates/plugins/cypress/test/.Dockerfile.cypress +22 -0
- stoobly_agent/app/cli/scaffold/templates/plugins/cypress/test/.docker-compose.test.yml +19 -0
- stoobly_agent/app/cli/scaffold/templates/plugins/playwright/test/.Dockerfile.playwright +33 -0
- stoobly_agent/app/cli/scaffold/templates/plugins/playwright/test/.docker-compose.test.yml +18 -0
- stoobly_agent/app/cli/scaffold/templates/plugins/playwright/test/.entrypoint.sh +11 -0
- stoobly_agent/app/cli/scaffold/templates/workflow/mock/bin/configure +2 -10
- stoobly_agent/app/cli/scaffold/templates/workflow/mock/docker-compose.yml +17 -0
- stoobly_agent/app/cli/scaffold/templates/workflow/record/bin/configure +19 -45
- stoobly_agent/app/cli/scaffold/templates/workflow/record/docker-compose.yml +17 -0
- stoobly_agent/app/cli/scaffold/templates/workflow/test/bin/configure +2 -10
- stoobly_agent/app/cli/scaffold/templates/workflow/test/docker-compose.yml +17 -0
- stoobly_agent/app/cli/scaffold/workflow_create_command.py +0 -1
- stoobly_agent/app/cli/scaffold/workflow_run_command.py +1 -1
- stoobly_agent/app/cli/scaffold_cli.py +85 -96
- stoobly_agent/app/proxy/handle_record_service.py +12 -3
- stoobly_agent/app/proxy/handle_replay_service.py +14 -2
- stoobly_agent/app/proxy/intercept_settings.py +12 -8
- stoobly_agent/app/proxy/record/upload_request_service.py +5 -8
- stoobly_agent/app/proxy/replay/replay_request_service.py +3 -0
- stoobly_agent/app/proxy/run.py +3 -28
- stoobly_agent/app/proxy/utils/allowed_request_service.py +3 -2
- stoobly_agent/app/proxy/utils/minimize_headers.py +47 -0
- stoobly_agent/app/proxy/utils/publish_change_service.py +22 -24
- stoobly_agent/app/proxy/utils/strategy.py +16 -0
- stoobly_agent/app/settings/__init__.py +15 -6
- stoobly_agent/app/settings/data_rules.py +25 -1
- stoobly_agent/app/settings/intercept_settings.py +5 -2
- stoobly_agent/app/settings/types/__init__.py +0 -1
- stoobly_agent/app/settings/ui_settings.py +5 -5
- stoobly_agent/cli.py +41 -16
- stoobly_agent/config/constants/custom_headers.py +1 -0
- stoobly_agent/config/constants/env_vars.py +4 -3
- stoobly_agent/config/constants/record_strategy.py +6 -0
- stoobly_agent/config/data_dir.py +1 -0
- stoobly_agent/config/settings.yml.sample +2 -3
- stoobly_agent/lib/logger.py +15 -5
- stoobly_agent/public/index.html +1 -1
- stoobly_agent/public/main-es2015.5a9aa16433404c3f423a.js +1 -0
- stoobly_agent/public/main-es5.5a9aa16433404c3f423a.js +1 -0
- stoobly_agent/test/app/cli/intercept/intercept_configure_test.py +231 -1
- stoobly_agent/test/app/cli/scaffold/cli_invoker.py +3 -2
- stoobly_agent/test/app/cli/scaffold/cli_test.py +3 -3
- stoobly_agent/test/app/cli/scaffold/e2e_test.py +11 -11
- stoobly_agent/test/app/models/schemas/.stoobly/db/VERSION +1 -1
- stoobly_agent/test/app/proxy/utils/minimize_headers_test.py +342 -0
- {stoobly_agent-1.9.12.dist-info → stoobly_agent-1.10.1.dist-info}/METADATA +2 -1
- {stoobly_agent-1.9.12.dist-info → stoobly_agent-1.10.1.dist-info}/RECORD +96 -80
- stoobly_agent/public/main-es2015.089b46f303768fbe864f.js +0 -1
- stoobly_agent/public/main-es5.089b46f303768fbe864f.js +0 -1
- {stoobly_agent-1.9.12.dist-info → stoobly_agent-1.10.1.dist-info}/LICENSE +0 -0
- {stoobly_agent-1.9.12.dist-info → stoobly_agent-1.10.1.dist-info}/WHEEL +0 -0
- {stoobly_agent-1.9.12.dist-info → stoobly_agent-1.10.1.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,47 @@
|
|
1
|
+
import pdb
|
2
|
+
|
3
|
+
from mitmproxy.http import Headers
|
4
|
+
from mitmproxy.http import HTTPFlow as MitmproxyHTTPFlow
|
5
|
+
|
6
|
+
from typing import Final
|
7
|
+
|
8
|
+
|
9
|
+
REQUEST_HEADERS_ALLOWLIST: Final[dict[str]] = {
|
10
|
+
"Accept",
|
11
|
+
"Accept-Encoding",
|
12
|
+
"Accept-Language",
|
13
|
+
"Content-Length",
|
14
|
+
"Content-Type",
|
15
|
+
"Host",
|
16
|
+
"Origin",
|
17
|
+
"Referer",
|
18
|
+
"User-Agent",
|
19
|
+
}
|
20
|
+
|
21
|
+
RESPONSE_HEADERS_ALLOWLIST: Final[dict[str]] = {
|
22
|
+
"Content-Length",
|
23
|
+
"Content-Type",
|
24
|
+
"Date",
|
25
|
+
"Transfer-Encoding",
|
26
|
+
"Server", # Sometimes required for HTTP/1.0, but not strictly mandatory
|
27
|
+
}
|
28
|
+
|
29
|
+
def minimize_headers(flow: MitmproxyHTTPFlow):
|
30
|
+
minimize_request_headers(flow)
|
31
|
+
minimize_response_headers(flow)
|
32
|
+
|
33
|
+
def minimize_request_headers(flow: MitmproxyHTTPFlow) -> None:
|
34
|
+
remove_headers(flow.request.headers, REQUEST_HEADERS_ALLOWLIST)
|
35
|
+
|
36
|
+
def minimize_response_headers(flow: MitmproxyHTTPFlow) -> None:
|
37
|
+
remove_headers(flow.response.headers, RESPONSE_HEADERS_ALLOWLIST)
|
38
|
+
|
39
|
+
def remove_headers(headers: Headers, allowlist: dict[str]):
|
40
|
+
keys_to_remove = []
|
41
|
+
|
42
|
+
for key in headers:
|
43
|
+
if key.lower() not in {allowed_header.lower() for allowed_header in allowlist}:
|
44
|
+
keys_to_remove.append(key)
|
45
|
+
|
46
|
+
for key in keys_to_remove:
|
47
|
+
headers.pop(key)
|
@@ -1,40 +1,35 @@
|
|
1
1
|
import pdb
|
2
|
+
import requests
|
2
3
|
import threading
|
3
4
|
|
4
|
-
from
|
5
|
-
|
6
|
-
from stoobly_agent.config.constants.statuses import REQUESTS_MODIFIED
|
5
|
+
from stoobly_agent.app.settings import Settings
|
6
|
+
from stoobly_agent.config.constants.statuses import REQUESTS_MODIFIED, SETTINGS_MODIFIED
|
7
7
|
from stoobly_agent.lib.api.agent_api import AgentApi
|
8
8
|
from stoobly_agent.lib.cache import Cache
|
9
9
|
from stoobly_agent.lib.logger import Logger
|
10
10
|
|
11
|
-
|
12
|
-
sync: bool
|
13
|
-
|
14
|
-
# Announce that a new request has been created
|
15
|
-
def publish_change(status: str, value: any, **options: Options):
|
16
|
-
if options.get('sync'):
|
17
|
-
return __publish_change_sync(status, value)
|
11
|
+
LOG_ID = 'PublishChange'
|
18
12
|
|
19
|
-
|
20
|
-
|
13
|
+
def publish_settings_modified(value):
|
14
|
+
return __publish_change_sync(SETTINGS_MODIFIED, value)
|
15
|
+
|
16
|
+
def publish_requests_modified(value):
|
17
|
+
settings: Settings = Settings.instance()
|
21
18
|
|
22
|
-
# If
|
23
|
-
if
|
24
|
-
return
|
19
|
+
# If not headless...
|
20
|
+
if settings.ui.active:
|
21
|
+
return __publish_change_sync(REQUESTS_MODIFIED, value)
|
25
22
|
|
26
23
|
ui_url = settings.ui.url
|
27
|
-
|
28
24
|
if not ui_url:
|
29
|
-
Logger.instance().warn('Settings.ui.url not configured')
|
30
25
|
return False
|
31
|
-
else:
|
32
|
-
thread = threading.Thread(target=__put_status, args=(ui_url, status, value))
|
33
|
-
thread.start()
|
34
|
-
return True
|
35
26
|
|
36
|
-
|
37
|
-
|
27
|
+
return __publish_change_async(REQUESTS_MODIFIED, value, ui_url)
|
28
|
+
|
29
|
+
def __publish_change_async(status, value, ui_url: str):
|
30
|
+
thread = threading.Thread(target=__put_status, args=(ui_url, status, value))
|
31
|
+
thread.start()
|
32
|
+
return True
|
38
33
|
|
39
34
|
def __publish_change_sync(status: str, value):
|
40
35
|
cache = Cache.instance()
|
@@ -43,4 +38,7 @@ def __publish_change_sync(status: str, value):
|
|
43
38
|
|
44
39
|
def __put_status(ui_url, status, value):
|
45
40
|
api: AgentApi = AgentApi(ui_url)
|
46
|
-
|
41
|
+
try:
|
42
|
+
api.update_status(status, value)
|
43
|
+
except requests.exceptions.ConnectionError:
|
44
|
+
Logger.instance(LOG_ID).error(f"could not connect to {ui_url}")
|
@@ -0,0 +1,16 @@
|
|
1
|
+
import pdb
|
2
|
+
|
3
|
+
from stoobly_agent.app.proxy.intercept_settings import InterceptSettings
|
4
|
+
from stoobly_agent.app.settings.constants import intercept_mode
|
5
|
+
from stoobly_agent.config.constants import request_origin
|
6
|
+
|
7
|
+
|
8
|
+
def get_active_mode_strategy(intercept_settings: InterceptSettings) -> str:
|
9
|
+
strategy = ""
|
10
|
+
|
11
|
+
if intercept_settings.mode == intercept_mode.RECORD:
|
12
|
+
strategy = intercept_settings.record_strategy
|
13
|
+
elif intercept_settings.mode == intercept_mode.TEST:
|
14
|
+
strategy = intercept_settings.test_strategy
|
15
|
+
|
16
|
+
return strategy
|
@@ -3,12 +3,12 @@ import pdb
|
|
3
3
|
import time
|
4
4
|
import yaml
|
5
5
|
|
6
|
-
from
|
6
|
+
from filelock import FileLock
|
7
7
|
from watchdog.observers import Observer
|
8
8
|
from watchdog.events import PatternMatchingEventHandler
|
9
9
|
from yamale import *
|
10
10
|
|
11
|
-
from stoobly_agent.config.constants import env_vars
|
11
|
+
from stoobly_agent.config.constants import env_vars
|
12
12
|
from stoobly_agent.config.data_dir import DataDir
|
13
13
|
from stoobly_agent.config.source_dir import SourceDir
|
14
14
|
from stoobly_agent.lib.logger import Logger
|
@@ -19,6 +19,7 @@ from .remote_settings import RemoteSettings
|
|
19
19
|
from .ui_settings import UISettings
|
20
20
|
|
21
21
|
LOG_ID = 'Settings'
|
22
|
+
SETTINGS_YML = 'settings.yml'
|
22
23
|
|
23
24
|
class Settings:
|
24
25
|
_instances = None
|
@@ -102,7 +103,7 @@ class Settings:
|
|
102
103
|
if self.__watching:
|
103
104
|
return False
|
104
105
|
|
105
|
-
patterns = [
|
106
|
+
patterns = [SETTINGS_YML]
|
106
107
|
ignore_patterns = None
|
107
108
|
ignore_directories = False
|
108
109
|
case_sensitive = True
|
@@ -177,7 +178,14 @@ class Settings:
|
|
177
178
|
self.__load_settings()
|
178
179
|
|
179
180
|
def write(self, contents):
|
180
|
-
if contents:
|
181
|
+
if not contents:
|
182
|
+
return
|
183
|
+
|
184
|
+
lock_file = f".{SETTINGS_YML}.lock"
|
185
|
+
lock_file_path = os.path.join(os.path.dirname(self.__settings_file_path), lock_file)
|
186
|
+
lock = FileLock(lock_file_path) # lock file alongside the target
|
187
|
+
|
188
|
+
with lock:
|
181
189
|
with open(self.__settings_file_path, 'w') as fp:
|
182
190
|
yaml.dump(contents, fp, allow_unicode=True)
|
183
191
|
|
@@ -209,13 +217,14 @@ class Settings:
|
|
209
217
|
|
210
218
|
def __reload_settings(self, event):
|
211
219
|
if not self.__load_lock:
|
212
|
-
from stoobly_agent.app.proxy.utils.publish_change_service import
|
220
|
+
from stoobly_agent.app.proxy.utils.publish_change_service import publish_settings_modified
|
213
221
|
|
214
222
|
self.__load_lock = True
|
215
223
|
|
216
224
|
Logger.instance(LOG_ID).debug('Reloading settings')
|
217
225
|
self.__load_settings()
|
218
226
|
|
219
|
-
|
227
|
+
if self.__ui_settings.active:
|
228
|
+
publish_settings_modified(self.__settings)
|
220
229
|
|
221
230
|
self.__load_lock = False
|
@@ -1,4 +1,4 @@
|
|
1
|
-
from stoobly_agent.config.constants import mock_policy, record_order, record_policy, replay_policy, test_strategy
|
1
|
+
from stoobly_agent.config.constants import mock_policy, record_order, record_policy, record_strategy, replay_policy, test_strategy
|
2
2
|
|
3
3
|
from .types.proxy_settings import DataRules as IDataRules
|
4
4
|
|
@@ -10,6 +10,7 @@ class DataRules:
|
|
10
10
|
self.__mock_policy = self.__data_rules.get('mock_policy') or mock_policy.FOUND
|
11
11
|
self.__record_order = self.__data_rules.get('record_order') or record_order.APPEND
|
12
12
|
self.__record_policy = self.__data_rules.get('record_policy') or record_policy.ALL
|
13
|
+
self.__record_strategy = self.__data_rules.get('record_strategy') or record_strategy.FULL
|
13
14
|
self.__replay_policy = self.__data_rules.get('replay_policy') or replay_policy.ALL
|
14
15
|
self.__scenario_key = self.__data_rules.get('scenario_key')
|
15
16
|
self.__test_policy = self.__data_rules.get('test_policy') or mock_policy.FOUND
|
@@ -46,6 +47,19 @@ class DataRules:
|
|
46
47
|
self.__record_order = v
|
47
48
|
self.__data_rules['record_order'] = v
|
48
49
|
|
50
|
+
@property
|
51
|
+
def record_strategy(self):
|
52
|
+
return self.__record_strategy
|
53
|
+
|
54
|
+
@record_strategy.setter
|
55
|
+
def record_strategy(self, v):
|
56
|
+
valid_strategies = [record_strategy.FULL, record_strategy.MINIMAL]
|
57
|
+
if v not in valid_strategies:
|
58
|
+
raise TypeError(f"record_strategy has to be one of {valid_strategies}, got {v}")
|
59
|
+
|
60
|
+
self.__record_strategy = v
|
61
|
+
self.__data_rules['record_strategy'] = v
|
62
|
+
|
49
63
|
@property
|
50
64
|
def replay_policy(self):
|
51
65
|
return self.__replay_policy
|
@@ -79,11 +93,21 @@ class DataRules:
|
|
79
93
|
def test_strategy(self):
|
80
94
|
return self.__test_strategy
|
81
95
|
|
96
|
+
@test_strategy.setter
|
97
|
+
def test_strategy(self, v):
|
98
|
+
valid_strategies = [test_strategy.CONTRACT, test_strategy.CUSTOM, test_strategy.DIFF, test_strategy.FUZZY]
|
99
|
+
if v not in valid_strategies:
|
100
|
+
raise TypeError(f"test_strategy has to be one of {valid_strategies}, got {v}")
|
101
|
+
|
102
|
+
self.__test_strategy = v
|
103
|
+
self.__data_rules['test_strategy'] = v
|
104
|
+
|
82
105
|
def to_dict(self) -> IDataRules:
|
83
106
|
return {
|
84
107
|
'mock_policy': self.__mock_policy,
|
85
108
|
'record_order': self.__record_order,
|
86
109
|
'record_policy': self.__record_policy,
|
110
|
+
'record_strategy': self.__record_strategy,
|
87
111
|
'replay_policy': self.__replay_policy,
|
88
112
|
'scenario_key': self.__scenario_key,
|
89
113
|
'test_policy': self.__test_policy,
|
@@ -19,6 +19,9 @@ class InterceptSettings:
|
|
19
19
|
|
20
20
|
@property
|
21
21
|
def active(self):
|
22
|
+
if os.environ.get(env_vars.AGENT_INTERCEPT_ACTIVE):
|
23
|
+
return os.environ[env_vars.AGENT_INTERCEPT_ACTIVE]
|
24
|
+
|
22
25
|
return self.__active
|
23
26
|
|
24
27
|
@active.setter
|
@@ -35,8 +38,8 @@ class InterceptSettings:
|
|
35
38
|
if self.__mode != self.mode_before_change:
|
36
39
|
return self.__mode
|
37
40
|
|
38
|
-
if os.environ.get(env_vars.
|
39
|
-
return os.environ[env_vars.
|
41
|
+
if os.environ.get(env_vars.AGENT_INTERCEPT_MODE):
|
42
|
+
return os.environ[env_vars.AGENT_INTERCEPT_MODE]
|
40
43
|
|
41
44
|
return self.__mode
|
42
45
|
|
@@ -21,8 +21,8 @@ class UISettings:
|
|
21
21
|
if self.__active != self.active_before_change:
|
22
22
|
return self.__active
|
23
23
|
|
24
|
-
if os.environ.get(env_vars.
|
25
|
-
return
|
24
|
+
if os.environ.get(env_vars.AGENT_HEADLESS):
|
25
|
+
return False
|
26
26
|
|
27
27
|
return self.__active or False
|
28
28
|
|
@@ -39,8 +39,8 @@ class UISettings:
|
|
39
39
|
if self.__url != self.url_before_change:
|
40
40
|
return self.__url
|
41
41
|
|
42
|
-
if os.environ.get(env_vars.
|
43
|
-
return os.environ[env_vars.
|
42
|
+
if os.environ.get(env_vars.AGENT_UI_URL):
|
43
|
+
return os.environ[env_vars.AGENT_UI_URL]
|
44
44
|
|
45
45
|
return self.__url or ''
|
46
46
|
|
@@ -52,4 +52,4 @@ class UISettings:
|
|
52
52
|
return {
|
53
53
|
'active': self.__active,
|
54
54
|
'url': self.__url,
|
55
|
-
}
|
55
|
+
}
|
stoobly_agent/cli.py
CHANGED
@@ -13,9 +13,10 @@ from stoobly_agent.app.proxy.constants import custom_response_codes
|
|
13
13
|
from stoobly_agent.app.proxy.replay.replay_request_service import replay as replay_request
|
14
14
|
from stoobly_agent.config.constants import env_vars, mode
|
15
15
|
from stoobly_agent.config.data_dir import DataDir
|
16
|
+
from stoobly_agent.lib.logger import Logger
|
16
17
|
from stoobly_agent.lib.utils.conditional_decorator import ConditionalDecorator
|
17
18
|
|
18
|
-
from .app.api import
|
19
|
+
from .app.api import run as run_api
|
19
20
|
from .app.cli import ca_cert, config, endpoint, feature, intercept, MainGroup, request, scenario, scaffold, snapshot, trace
|
20
21
|
from .app.cli.helpers.feature_flags import local, remote
|
21
22
|
from .app.settings import Settings
|
@@ -99,6 +100,7 @@ def init(**kwargs):
|
|
99
100
|
''')
|
100
101
|
@click.option('--headless', is_flag=True, default=False, help='Disable starting UI.')
|
101
102
|
@click.option('--intercept', is_flag=True, default=False, help='Enable intercept on run.')
|
103
|
+
@click.option('--intercept-mode', help='Set intercept mode.')
|
102
104
|
@click.option('--log-level', default=logger.INFO, type=click.Choice([logger.DEBUG, logger.INFO, logger.WARNING, logger.ERROR]), help='''
|
103
105
|
Log levels can be "debug", "info", "warning", or "error"
|
104
106
|
''')
|
@@ -124,7 +126,19 @@ def init(**kwargs):
|
|
124
126
|
def run(**kwargs):
|
125
127
|
from .app.proxy.run import run as run_proxy
|
126
128
|
|
127
|
-
|
129
|
+
# Observe config for changes
|
130
|
+
settings: Settings = Settings.instance()
|
131
|
+
settings.watch()
|
132
|
+
|
133
|
+
if kwargs.get('headless'):
|
134
|
+
os.environ[env_vars.AGENT_HEADLESS] = '1'
|
135
|
+
|
136
|
+
if kwargs.get('intercept'):
|
137
|
+
os.environ[env_vars.AGENT_INTERCEPT_ACTIVE] = '1'
|
138
|
+
|
139
|
+
if kwargs.get('intercept_mode'):
|
140
|
+
os.environ[env_vars.AGENT_INTERCEPT_MODE] = kwargs['intercept_mode']
|
141
|
+
settings.proxy.intercept.mode = kwargs['intercept_mode']
|
128
142
|
|
129
143
|
if kwargs.get('lifecycle_hooks_path'):
|
130
144
|
os.environ[env_vars.AGENT_LIFECYCLE_HOOKS_PATH] = kwargs['lifecycle_hooks_path']
|
@@ -135,21 +149,32 @@ def run(**kwargs):
|
|
135
149
|
if kwargs.get('response_fixtures_path'):
|
136
150
|
os.environ[env_vars.AGENT_RESPONSE_FIXTURES_PATH] = kwargs['response_fixtures_path']
|
137
151
|
|
138
|
-
# Observe config for changes
|
139
|
-
Settings.instance().watch()
|
140
|
-
|
141
152
|
if not os.getenv(env_vars.LOG_LEVEL):
|
142
|
-
|
143
|
-
|
144
|
-
if
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
+
os.environ[env_vars.LOG_LEVEL] = kwargs['log_level']
|
154
|
+
|
155
|
+
if kwargs.get('api_url'):
|
156
|
+
os.environ[env_vars.API_URL] = kwargs['api_url']
|
157
|
+
|
158
|
+
if not kwargs.get('headless'):
|
159
|
+
ui_url = f"http://{kwargs['ui_host']}:{kwargs['ui_port']}"
|
160
|
+
os.environ[env_vars.AGENT_UI_URL] = ui_url
|
161
|
+
settings.ui.active = True
|
162
|
+
settings.ui.url = ui_url
|
163
|
+
|
164
|
+
if not kwargs.get('proxyless'):
|
165
|
+
proxy_url = f"http://{kwargs['proxy_host']}:{kwargs['proxy_port']}"
|
166
|
+
os.environ[env_vars.AGENT_PROXY_URL] = proxy_url
|
167
|
+
settings.proxy.url = proxy_url
|
168
|
+
|
169
|
+
if not kwargs.get('headless'):
|
170
|
+
settings.commit()
|
171
|
+
run_api(**kwargs)
|
172
|
+
|
173
|
+
if not kwargs.get('proxyless'):
|
174
|
+
log_id = 'Proxy'
|
175
|
+
Logger.instance(log_id).info(f"starting with mode {kwargs['proxy_mode']} and listening at {kwargs['proxy_host']}:{kwargs['proxy_port']}")
|
176
|
+
Logger.instance(log_id).info(f"{'' if settings.proxy.intercept.active else 'not yet '}configured to {settings.proxy.intercept.mode}")
|
177
|
+
run_proxy(**kwargs)
|
153
178
|
|
154
179
|
@main.command(
|
155
180
|
help="Mock request"
|
@@ -13,6 +13,7 @@ PROJECT_KEY = 'X-Stoobly-Project-Key'
|
|
13
13
|
PROXY_MODE = 'X-Stoobly-Proxy-Mode'
|
14
14
|
PUBLIC_DIRECTORY_PATH = 'X-Stoobly-Public-Directory-Path'
|
15
15
|
RECORD_POLICY = 'X-Stoobly-Record-Policy'
|
16
|
+
RECORD_STRATEGY = 'X-Stoobly-Record-Strategy'
|
16
17
|
REPORT_KEY = 'X-Stoobly-Report-Key'
|
17
18
|
REQUEST_ORIGIN = 'X-Stoobly-Request-Origin'
|
18
19
|
RESPONSE_FIXTURES_PATH = 'X-Stoobly-Response-Fixtures-Path'
|
@@ -1,9 +1,10 @@
|
|
1
|
-
AGENT_ACTIVE_MODE = 'STOOBLY_AGENT_ACTIVE_MODE'
|
2
1
|
AGENT_CONFIG_PATH = 'STOOBLY_AGENT_CONFIG_PATH'
|
3
2
|
AGENT_ENABLED = 'STOOBLY_AGENT_ENABLED'
|
4
3
|
AGENT_SELF_INTERCEPT_ENABLED = 'STOOBLY_AGENT_SELF_INTERCEPT_ENABLED'
|
4
|
+
AGENT_HEADLESS = 'STOOBLY_AGENT_HEADLESS'
|
5
5
|
AGENT_INCLUDE_PATTERNS = 'STOOBLY_AGENT_INCLUDE_PATTERNS'
|
6
|
-
|
6
|
+
AGENT_INTERCEPT_ACTIVE = 'STOOBLY_AGENT_INTERCEPT_ACTIVE'
|
7
|
+
AGENT_INTERCEPT_MODE = 'STOOBLY_AGENT_INTERCEPT_MODE'
|
7
8
|
AGENT_EXCLUDE_PATTERNS = 'STOOBLY_AGENT_EXCLUDE_PATTERNS'
|
8
9
|
AGENT_LIFECYCLE_HOOKS_PATH = 'STOOBLY_AGENT_LIFECYCLE_HOOKS_PATH'
|
9
10
|
AGENT_POLICY = 'STOOBLY_AGENT_POLICY'
|
@@ -18,7 +19,7 @@ AGENT_RESPONSE_FIXTURES_PATH = 'STOOBLY_AGENT_RESPONSE_FIXTURES_PATH'
|
|
18
19
|
AGENT_SERVICE_URL = 'STOOBLY_AGENT_SERVICE_URL'
|
19
20
|
AGENT_SCENARIO_KEY = 'STOOBLY_AGENT_SCENARIO_KEY'
|
20
21
|
AGENT_SIMULATE_LATENCY = 'STOOBLY_AGENT_SIMULATE_LATENCY'
|
21
|
-
|
22
|
+
AGENT_UI_URL = 'STOOBLY_AGENT_UI_URL'
|
22
23
|
API_URL = 'STOOBLY_API_URL'
|
23
24
|
API_KEY = 'STOOBLY_API_KEY'
|
24
25
|
ENV = 'STOOBLY_AGENT_ENV'
|
stoobly_agent/config/data_dir.py
CHANGED
@@ -12,10 +12,9 @@ proxy:
|
|
12
12
|
project_key: eyJpIjowLCJvIjowfQ==
|
13
13
|
firewall: {}
|
14
14
|
rewrite: {}
|
15
|
-
proxy_config_path: config/settings.yml
|
16
15
|
remote:
|
17
16
|
api_key: ''
|
18
|
-
api_url: '
|
17
|
+
api_url: ''
|
19
18
|
ui:
|
20
19
|
active: false
|
21
|
-
url:
|
20
|
+
url: ''
|
stoobly_agent/lib/logger.py
CHANGED
@@ -27,6 +27,7 @@ LogLevel = Literal[DEBUG, ERROR, INFO, WARNING]
|
|
27
27
|
|
28
28
|
class Logger:
|
29
29
|
_instance = None
|
30
|
+
_instances = {}
|
30
31
|
|
31
32
|
def __init__(self):
|
32
33
|
raise RuntimeError('Call instance() instead')
|
@@ -34,14 +35,23 @@ class Logger:
|
|
34
35
|
@classmethod
|
35
36
|
def instance(cls, name = None):
|
36
37
|
if cls._instance is None:
|
38
|
+
logging.getLogger("mitmproxy").setLevel(logging.ERROR)
|
37
39
|
cls._instance = cls.__new__(cls)
|
38
|
-
|
39
|
-
cls._instance.load()
|
40
|
+
cls._instance.load()
|
40
41
|
|
41
42
|
if not name:
|
42
|
-
|
43
|
-
|
44
|
-
|
43
|
+
name = 'root'
|
44
|
+
|
45
|
+
logger = logging.getLogger(name)
|
46
|
+
|
47
|
+
if name not in cls._instances:
|
48
|
+
handler = logging.StreamHandler()
|
49
|
+
handler.setFormatter(logging.Formatter(f"[%(levelname)s] {name} %(message)s"))
|
50
|
+
logger.addHandler(handler)
|
51
|
+
logger.propagate = False
|
52
|
+
cls._instances[name] = logger
|
53
|
+
|
54
|
+
return logger
|
45
55
|
|
46
56
|
@classmethod
|
47
57
|
def reload(cls):
|
stoobly_agent/public/index.html
CHANGED
@@ -113,6 +113,6 @@
|
|
113
113
|
</div>
|
114
114
|
|
115
115
|
<root></root>
|
116
|
-
<script src="runtime-es2015.f8c814b38b27708e91c1.js" type="module"></script><script src="runtime-es5.f8c814b38b27708e91c1.js" nomodule="" defer=""></script><script src="polyfills-es5.7530172ddcec11a10eb3.js" nomodule="" defer=""></script><script src="polyfills-es2015.8ce2adc69f283f6c4c5e.js" type="module"></script><script src="main-es2015.
|
116
|
+
<script src="runtime-es2015.f8c814b38b27708e91c1.js" type="module"></script><script src="runtime-es5.f8c814b38b27708e91c1.js" nomodule="" defer=""></script><script src="polyfills-es5.7530172ddcec11a10eb3.js" nomodule="" defer=""></script><script src="polyfills-es2015.8ce2adc69f283f6c4c5e.js" type="module"></script><script src="main-es2015.5a9aa16433404c3f423a.js" type="module"></script><script src="main-es5.5a9aa16433404c3f423a.js" nomodule="" defer=""></script></body>
|
117
117
|
|
118
118
|
</html>
|