stoobly-agent 1.9.12__py3-none-any.whl → 1.10.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- stoobly_agent/__init__.py +1 -1
- stoobly_agent/app/api/__init__.py +4 -20
- 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 +13 -0
- stoobly_agent/app/cli/scaffold/docker/constants.py +4 -6
- stoobly_agent/app/cli/scaffold/docker/service/builder.py +19 -4
- stoobly_agent/app/cli/scaffold/docker/workflow/builder.py +0 -18
- stoobly_agent/app/cli/scaffold/docker/workflow/command_decorator.py +24 -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 +144 -21
- 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/templates/app/.Dockerfile.context +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/docker-compose.yml +16 -6
- stoobly_agent/app/cli/scaffold/templates/app/build/test/docker-compose.yml +16 -6
- stoobly_agent/app/cli/scaffold/templates/app/entrypoint/mock/docker-compose.yml +16 -10
- stoobly_agent/app/cli/scaffold/templates/app/entrypoint/record/docker-compose.yml +16 -10
- 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/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/docker-compose.yml +17 -0
- stoobly_agent/app/cli/scaffold/templates/workflow/record/docker-compose.yml +17 -0
- 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 +68 -77
- 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 +11 -7
- stoobly_agent/app/proxy/record/upload_request_service.py +2 -2
- 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 +5 -4
- stoobly_agent/app/proxy/utils/strategy.py +16 -0
- stoobly_agent/app/settings/__init__.py +9 -3
- 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/settings.yml.sample +2 -3
- stoobly_agent/lib/logger.py +15 -5
- 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/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.0.dist-info}/METADATA +2 -1
- {stoobly_agent-1.9.12.dist-info → stoobly_agent-1.10.0.dist-info}/RECORD +74 -58
- {stoobly_agent-1.9.12.dist-info → stoobly_agent-1.10.0.dist-info}/LICENSE +0 -0
- {stoobly_agent-1.9.12.dist-info → stoobly_agent-1.10.0.dist-info}/WHEEL +0 -0
- {stoobly_agent-1.9.12.dist-info → stoobly_agent-1.10.0.dist-info}/entry_points.txt +0 -0
stoobly_agent/app/proxy/run.py
CHANGED
@@ -16,8 +16,6 @@ tls.DEFAULT_OPTIONS |= 0x4
|
|
16
16
|
from mitmproxy.options import Options
|
17
17
|
from mitmproxy.tools.dump import DumpMaster
|
18
18
|
|
19
|
-
from stoobly_agent.app.settings import Settings
|
20
|
-
|
21
19
|
INTERCEPT_HANDLER_FILENAME = 'intercept_handler.py'
|
22
20
|
|
23
21
|
def run(**kwargs):
|
@@ -67,7 +65,6 @@ def __with_static_options(config: MitmproxyConfig, cli_options):
|
|
67
65
|
config.set(options)
|
68
66
|
|
69
67
|
def __with_cli_options(config: MitmproxyConfig, cli_options: dict):
|
70
|
-
__commit_options(cli_options)
|
71
68
|
__filter_options(cli_options)
|
72
69
|
|
73
70
|
options = []
|
@@ -86,31 +83,6 @@ def __with_cli_options(config: MitmproxyConfig, cli_options: dict):
|
|
86
83
|
|
87
84
|
config.set(tuple(options))
|
88
85
|
|
89
|
-
def __commit_options(options: dict):
|
90
|
-
changed = False
|
91
|
-
service_name = os.environ.get(SERVICE_NAME_ENV)
|
92
|
-
settings = Settings.instance()
|
93
|
-
|
94
|
-
if not service_name or options.get('intercept'):
|
95
|
-
# In the case when service name is set,
|
96
|
-
# only update if intercept is explicitly enabled
|
97
|
-
intercept = not not options.get('intercept')
|
98
|
-
changed = settings.proxy.intercept.active != intercept
|
99
|
-
settings.proxy.intercept.active = intercept
|
100
|
-
|
101
|
-
if not service_name or service_name == CORE_MOCK_UI_SERVICE_NAME:
|
102
|
-
# Causes potentially unintended side effects when run as part of scaffold
|
103
|
-
# Defer to ui service for configuration in this case
|
104
|
-
if options.get('proxy_host') and options.get('proxy_port'):
|
105
|
-
settings.proxy.url = f"http://{options.get('proxy_host')}:{options.get('proxy_port')}"
|
106
|
-
|
107
|
-
settings.ui.active = not options.get('headless')
|
108
|
-
|
109
|
-
changed = True
|
110
|
-
|
111
|
-
if changed:
|
112
|
-
settings.commit()
|
113
|
-
|
114
86
|
def __filter_options(options):
|
115
87
|
'''
|
116
88
|
Filter out non-mitmproxy options
|
@@ -139,6 +111,9 @@ def __filter_options(options):
|
|
139
111
|
if 'intercept' in options:
|
140
112
|
del options['intercept']
|
141
113
|
|
114
|
+
if 'intercept_mode' in options:
|
115
|
+
del options['intercept_mode']
|
116
|
+
|
142
117
|
if 'lifecycle_hooks_path' in options:
|
143
118
|
del options['lifecycle_hooks_path']
|
144
119
|
|
@@ -5,13 +5,14 @@ from mitmproxy.http import Request as MitmproxyRequest
|
|
5
5
|
from typing import List
|
6
6
|
|
7
7
|
from stoobly_agent.app.proxy.intercept_settings import InterceptSettings
|
8
|
+
from stoobly_agent.app.settings.constants import intercept_mode
|
8
9
|
from stoobly_agent.app.settings.firewall_rule import FirewallRule
|
9
|
-
from stoobly_agent.config.constants import intercept_policy, request_origin
|
10
|
+
from stoobly_agent.config.constants import mode, intercept_policy, request_origin
|
10
11
|
from stoobly_agent.lib.logger import bcolors, Logger
|
11
12
|
|
12
13
|
LOG_ID = 'Firewall'
|
13
14
|
|
14
|
-
def get_active_mode_policy(request: MitmproxyRequest, intercept_settings: InterceptSettings):
|
15
|
+
def get_active_mode_policy(request: MitmproxyRequest, intercept_settings: InterceptSettings) -> str:
|
15
16
|
if intercept_settings.request_origin == request_origin.CLI:
|
16
17
|
return intercept_settings.policy
|
17
18
|
|
@@ -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,8 +1,10 @@
|
|
1
|
+
import os
|
1
2
|
import pdb
|
2
3
|
import threading
|
3
4
|
|
4
5
|
from typing import TypedDict
|
5
6
|
|
7
|
+
from stoobly_agent.app.settings import Settings
|
6
8
|
from stoobly_agent.config.constants.statuses import REQUESTS_MODIFIED
|
7
9
|
from stoobly_agent.lib.api.agent_api import AgentApi
|
8
10
|
from stoobly_agent.lib.cache import Cache
|
@@ -12,15 +14,14 @@ class Options(TypedDict):
|
|
12
14
|
sync: bool
|
13
15
|
|
14
16
|
# Announce that a new request has been created
|
15
|
-
def publish_change(status: str, value
|
17
|
+
def publish_change(status: str, value, **options: Options):
|
16
18
|
if options.get('sync'):
|
17
19
|
return __publish_change_sync(status, value)
|
18
20
|
|
19
|
-
|
20
|
-
settings = Settings.instance()
|
21
|
+
settings: Settings = Settings.instance()
|
21
22
|
|
22
23
|
# If ui is not active, return
|
23
|
-
if
|
24
|
+
if not settings.ui.active:
|
24
25
|
return False
|
25
26
|
|
26
27
|
ui_url = settings.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,7 +3,7 @@ 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 *
|
@@ -177,8 +177,14 @@ class Settings:
|
|
177
177
|
self.__load_settings()
|
178
178
|
|
179
179
|
def write(self, contents):
|
180
|
-
if contents:
|
181
|
-
|
180
|
+
if not contents:
|
181
|
+
return
|
182
|
+
|
183
|
+
path = self.__settings_file_path
|
184
|
+
lock = FileLock(path + ".lock") # lock file alongside the target
|
185
|
+
|
186
|
+
with lock:
|
187
|
+
with open(path, 'w') as fp:
|
182
188
|
yaml.dump(contents, fp, allow_unicode=True)
|
183
189
|
|
184
190
|
### Helpers
|
@@ -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'
|
@@ -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):
|