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.
Files changed (74) hide show
  1. stoobly_agent/__init__.py +1 -1
  2. stoobly_agent/app/api/__init__.py +4 -20
  3. stoobly_agent/app/api/configs_controller.py +3 -3
  4. stoobly_agent/app/cli/decorators/exec.py +1 -1
  5. stoobly_agent/app/cli/helpers/handle_config_update_service.py +4 -0
  6. stoobly_agent/app/cli/intercept_cli.py +40 -7
  7. stoobly_agent/app/cli/scaffold/app_command.py +4 -0
  8. stoobly_agent/app/cli/scaffold/app_config.py +21 -3
  9. stoobly_agent/app/cli/scaffold/app_create_command.py +109 -2
  10. stoobly_agent/app/cli/scaffold/constants.py +13 -0
  11. stoobly_agent/app/cli/scaffold/docker/constants.py +4 -6
  12. stoobly_agent/app/cli/scaffold/docker/service/builder.py +19 -4
  13. stoobly_agent/app/cli/scaffold/docker/workflow/builder.py +0 -18
  14. stoobly_agent/app/cli/scaffold/docker/workflow/command_decorator.py +24 -0
  15. stoobly_agent/app/cli/scaffold/docker/workflow/decorators_factory.py +7 -2
  16. stoobly_agent/app/cli/scaffold/docker/workflow/detached_decorator.py +42 -0
  17. stoobly_agent/app/cli/scaffold/docker/workflow/local_decorator.py +26 -0
  18. stoobly_agent/app/cli/scaffold/docker/workflow/mock_decorator.py +9 -10
  19. stoobly_agent/app/cli/scaffold/docker/workflow/reverse_proxy_decorator.py +5 -8
  20. stoobly_agent/app/cli/scaffold/service_config.py +144 -21
  21. stoobly_agent/app/cli/scaffold/service_create_command.py +11 -2
  22. stoobly_agent/app/cli/scaffold/service_dependency.py +51 -0
  23. stoobly_agent/app/cli/scaffold/templates/app/.Dockerfile.context +1 -1
  24. stoobly_agent/app/cli/scaffold/templates/app/build/mock/docker-compose.yml +16 -6
  25. stoobly_agent/app/cli/scaffold/templates/app/build/record/docker-compose.yml +16 -6
  26. stoobly_agent/app/cli/scaffold/templates/app/build/test/docker-compose.yml +16 -6
  27. stoobly_agent/app/cli/scaffold/templates/app/entrypoint/mock/docker-compose.yml +16 -10
  28. stoobly_agent/app/cli/scaffold/templates/app/entrypoint/record/docker-compose.yml +16 -10
  29. stoobly_agent/app/cli/scaffold/templates/app/entrypoint/test/docker-compose.yml +16 -10
  30. stoobly_agent/app/cli/scaffold/templates/app/stoobly-ui/.docker-compose.base.yml +2 -1
  31. stoobly_agent/app/cli/scaffold/templates/app/stoobly-ui/mock/.docker-compose.mock.yml +6 -3
  32. stoobly_agent/app/cli/scaffold/templates/app/stoobly-ui/record/.docker-compose.record.yml +6 -4
  33. stoobly_agent/app/cli/scaffold/templates/constants.py +4 -0
  34. stoobly_agent/app/cli/scaffold/templates/plugins/cypress/test/.Dockerfile.cypress +22 -0
  35. stoobly_agent/app/cli/scaffold/templates/plugins/cypress/test/.docker-compose.test.yml +19 -0
  36. stoobly_agent/app/cli/scaffold/templates/plugins/playwright/test/.Dockerfile.playwright +33 -0
  37. stoobly_agent/app/cli/scaffold/templates/plugins/playwright/test/.docker-compose.test.yml +18 -0
  38. stoobly_agent/app/cli/scaffold/templates/plugins/playwright/test/.entrypoint.sh +11 -0
  39. stoobly_agent/app/cli/scaffold/templates/workflow/mock/docker-compose.yml +17 -0
  40. stoobly_agent/app/cli/scaffold/templates/workflow/record/docker-compose.yml +17 -0
  41. stoobly_agent/app/cli/scaffold/templates/workflow/test/docker-compose.yml +17 -0
  42. stoobly_agent/app/cli/scaffold/workflow_create_command.py +0 -1
  43. stoobly_agent/app/cli/scaffold/workflow_run_command.py +1 -1
  44. stoobly_agent/app/cli/scaffold_cli.py +68 -77
  45. stoobly_agent/app/proxy/handle_record_service.py +12 -3
  46. stoobly_agent/app/proxy/handle_replay_service.py +14 -2
  47. stoobly_agent/app/proxy/intercept_settings.py +11 -7
  48. stoobly_agent/app/proxy/record/upload_request_service.py +2 -2
  49. stoobly_agent/app/proxy/replay/replay_request_service.py +3 -0
  50. stoobly_agent/app/proxy/run.py +3 -28
  51. stoobly_agent/app/proxy/utils/allowed_request_service.py +3 -2
  52. stoobly_agent/app/proxy/utils/minimize_headers.py +47 -0
  53. stoobly_agent/app/proxy/utils/publish_change_service.py +5 -4
  54. stoobly_agent/app/proxy/utils/strategy.py +16 -0
  55. stoobly_agent/app/settings/__init__.py +9 -3
  56. stoobly_agent/app/settings/data_rules.py +25 -1
  57. stoobly_agent/app/settings/intercept_settings.py +5 -2
  58. stoobly_agent/app/settings/types/__init__.py +0 -1
  59. stoobly_agent/app/settings/ui_settings.py +5 -5
  60. stoobly_agent/cli.py +41 -16
  61. stoobly_agent/config/constants/custom_headers.py +1 -0
  62. stoobly_agent/config/constants/env_vars.py +4 -3
  63. stoobly_agent/config/constants/record_strategy.py +6 -0
  64. stoobly_agent/config/settings.yml.sample +2 -3
  65. stoobly_agent/lib/logger.py +15 -5
  66. stoobly_agent/test/app/cli/intercept/intercept_configure_test.py +231 -1
  67. stoobly_agent/test/app/cli/scaffold/cli_invoker.py +3 -2
  68. stoobly_agent/test/app/models/schemas/.stoobly/db/VERSION +1 -1
  69. stoobly_agent/test/app/proxy/utils/minimize_headers_test.py +342 -0
  70. {stoobly_agent-1.9.12.dist-info → stoobly_agent-1.10.0.dist-info}/METADATA +2 -1
  71. {stoobly_agent-1.9.12.dist-info → stoobly_agent-1.10.0.dist-info}/RECORD +74 -58
  72. {stoobly_agent-1.9.12.dist-info → stoobly_agent-1.10.0.dist-info}/LICENSE +0 -0
  73. {stoobly_agent-1.9.12.dist-info → stoobly_agent-1.10.0.dist-info}/WHEEL +0 -0
  74. {stoobly_agent-1.9.12.dist-info → stoobly_agent-1.10.0.dist-info}/entry_points.txt +0 -0
@@ -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: any, **options: Options):
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
- from stoobly_agent.app.settings import Settings
20
- settings = Settings.instance()
21
+ settings: Settings = Settings.instance()
21
22
 
22
23
  # If ui is not active, return
23
- if not settings.ui.active:
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 shutil import copyfile
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
- with open(self.__settings_file_path, 'w') as fp:
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.AGENT_ACTIVE_MODE):
39
- return os.environ[env_vars.AGENT_ACTIVE_MODE]
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
 
@@ -84,7 +84,6 @@ class ISettings(TypedDict):
84
84
  api_url: str
85
85
  api_key: str
86
86
  mode: ISettingsMode
87
- proxy_config_path: str
88
87
 
89
88
  Component = {
90
89
  'Header': 'Header',
@@ -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.AGENT_IS_HEADLESS):
25
- return True
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.AGENT_URL):
43
- return os.environ[env_vars.AGENT_URL]
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 initialize as initialize_api, run as run_api
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
- os.environ[env_vars.AGENT_PROXY_URL] = f"http://{kwargs['proxy_host']}:{kwargs['proxy_port']}"
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
- os.environ[env_vars.LOG_LEVEL] = kwargs['log_level']
143
-
144
- if 'api_url' in kwargs and kwargs['api_url']:
145
- os.environ[env_vars.API_URL] = kwargs['api_url']
146
-
147
- url = initialize_api(**kwargs)
148
- if 'headless' in kwargs and not kwargs['headless']:
149
- run_api(url)
150
-
151
- if 'proxyless' in kwargs and not kwargs['proxyless']:
152
- run_proxy(**kwargs)
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
- AGENT_IS_HEADLESS = 'STOOBLY_AGENT_IS_HEADLESS'
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
- AGENT_URL = 'STOOBLY_AGENT_URL'
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'
@@ -0,0 +1,6 @@
1
+ from typing import Literal
2
+
3
+ FULL = 'full'
4
+ MINIMAL = 'minimal'
5
+
6
+ RecordStrategy = Literal[FULL, MINIMAL]
@@ -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: 'http://localhost:3000'
17
+ api_url: ''
19
18
  ui:
20
19
  active: false
21
- url: http://localhost:4200
20
+ url: ''
@@ -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
- return logging
43
- else:
44
- return logging.getLogger(name)
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):