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
@@ -0,0 +1,26 @@
|
|
1
|
+
from .builder import WorkflowBuilder
|
2
|
+
|
3
|
+
class LocalDecorator():
|
4
|
+
|
5
|
+
def __init__(self, workflow_builder: WorkflowBuilder):
|
6
|
+
self.__workflow_builder = workflow_builder
|
7
|
+
|
8
|
+
@property
|
9
|
+
def workflow_builder(self):
|
10
|
+
return self.__workflow_builder
|
11
|
+
|
12
|
+
@property
|
13
|
+
def service_builder(self):
|
14
|
+
return self.workflow_builder.service_builder
|
15
|
+
|
16
|
+
def decorate(self):
|
17
|
+
proxy_name = self.workflow_builder.proxy
|
18
|
+
services = self.workflow_builder.services
|
19
|
+
|
20
|
+
if not proxy_name in services:
|
21
|
+
services[proxy_name] = {}
|
22
|
+
|
23
|
+
if 'extra_hosts' not in services[proxy_name]:
|
24
|
+
services[proxy_name]['extra_hosts'] = []
|
25
|
+
|
26
|
+
services[proxy_name]['extra_hosts'].append('host.docker.internal:host-gateway')
|
@@ -1,17 +1,16 @@
|
|
1
1
|
import os
|
2
2
|
import pdb
|
3
3
|
|
4
|
-
from ...constants import
|
4
|
+
from ...constants import (
|
5
|
+
SERVICE_HOSTNAME, SERVICE_PORT, STOOBLY_CERTS_DIR,
|
6
|
+
)
|
5
7
|
from .builder import WorkflowBuilder
|
8
|
+
from .command_decorator import CommandDecorator
|
6
9
|
|
7
|
-
class MockDecorator():
|
10
|
+
class MockDecorator(CommandDecorator):
|
8
11
|
|
9
12
|
def __init__(self, workflow_builder: WorkflowBuilder):
|
10
|
-
|
11
|
-
|
12
|
-
@property
|
13
|
-
def workflow_builder(self):
|
14
|
-
return self.__workflow_builder
|
13
|
+
super().__init__(workflow_builder)
|
15
14
|
|
16
15
|
@property
|
17
16
|
def service_builder(self):
|
@@ -24,7 +23,7 @@ class MockDecorator():
|
|
24
23
|
'--headless',
|
25
24
|
'--intercept',
|
26
25
|
'--lifecycle-hooks-path', 'lifecycle_hooks.py',
|
27
|
-
'--proxy-mode',
|
26
|
+
'--proxy-mode', self.proxy_mode,
|
28
27
|
'--proxy-port', f"{SERVICE_PORT}",
|
29
28
|
'--public-directory-path', 'public',
|
30
29
|
'--response-fixtures-path', 'fixtures.yml',
|
@@ -35,8 +34,8 @@ class MockDecorator():
|
|
35
34
|
command.append('--certs')
|
36
35
|
command.append(os.path.join(STOOBLY_CERTS_DIR, f"{SERVICE_HOSTNAME}-joined.pem"))
|
37
36
|
|
38
|
-
services = self.
|
39
|
-
proxy_name = self.
|
37
|
+
services = self.workflow_builder.services
|
38
|
+
proxy_name = self.workflow_builder.proxy
|
40
39
|
proxy_service = services.get(proxy_name) or {}
|
41
40
|
|
42
41
|
services[proxy_name] = {
|
@@ -3,17 +3,14 @@ import pdb
|
|
3
3
|
|
4
4
|
from urllib.parse import urlparse
|
5
5
|
|
6
|
-
from ...constants import SERVICE_HOSTNAME, SERVICE_PORT,
|
6
|
+
from ...constants import SERVICE_HOSTNAME, SERVICE_PORT, STOOBLY_CERTS_DIR
|
7
7
|
from .builder import WorkflowBuilder
|
8
|
+
from .command_decorator import CommandDecorator
|
8
9
|
|
9
|
-
class ReverseProxyDecorator():
|
10
|
+
class ReverseProxyDecorator(CommandDecorator):
|
10
11
|
|
11
12
|
def __init__(self, workflow_builder: WorkflowBuilder):
|
12
|
-
|
13
|
-
|
14
|
-
@property
|
15
|
-
def workflow_builder(self):
|
16
|
-
return self.__workflow_builder
|
13
|
+
super().__init__(workflow_builder)
|
17
14
|
|
18
15
|
@property
|
19
16
|
def service_builder(self):
|
@@ -25,7 +22,7 @@ class ReverseProxyDecorator():
|
|
25
22
|
command = [
|
26
23
|
'--headless',
|
27
24
|
'--lifecycle-hooks-path', 'lifecycle_hooks.py',
|
28
|
-
'--proxy-mode',
|
25
|
+
'--proxy-mode', self.proxy_mode,
|
29
26
|
'--proxy-port', f"{SERVICE_PORT}",
|
30
27
|
'--ssl-insecure'
|
31
28
|
]
|
@@ -1,17 +1,22 @@
|
|
1
1
|
# Wraps the .config.yml file in the service folder
|
2
2
|
import hashlib
|
3
|
-
import os
|
4
3
|
import pdb
|
4
|
+
import re
|
5
5
|
|
6
6
|
from .config import Config
|
7
7
|
from .constants import (
|
8
8
|
SERVICE_DETACHED_ENV,
|
9
9
|
SERVICE_HOSTNAME_ENV,
|
10
10
|
SERVICE_ID_ENV,
|
11
|
+
SERVICE_LOCAL_ENV,
|
12
|
+
SERVICE_NAME_ENV,
|
11
13
|
SERVICE_PRIORITY_ENV,
|
12
14
|
SERVICE_PORT_ENV,
|
13
15
|
SERVICE_PROXY_MODE_ENV,
|
14
|
-
SERVICE_SCHEME_ENV
|
16
|
+
SERVICE_SCHEME_ENV,
|
17
|
+
SERVICE_UPSTREAM_HOSTNAME_ENV,
|
18
|
+
SERVICE_UPSTREAM_PORT_ENV,
|
19
|
+
SERVICE_UPSTREAM_SCHEME_ENV,
|
15
20
|
)
|
16
21
|
|
17
22
|
class ServiceConfig(Config):
|
@@ -21,10 +26,15 @@ class ServiceConfig(Config):
|
|
21
26
|
|
22
27
|
self.__detached = None
|
23
28
|
self.__hostname = None
|
29
|
+
self.__local = None
|
30
|
+
self.__name = None
|
24
31
|
self.__port = None
|
25
32
|
self.__priority = None
|
26
33
|
self.__proxy_mode = None
|
27
34
|
self.__scheme = None
|
35
|
+
self.__upstream_hostname = None
|
36
|
+
self.__upstream_port = None
|
37
|
+
self.__upstream_scheme = None
|
28
38
|
|
29
39
|
self.load()
|
30
40
|
|
@@ -34,6 +44,14 @@ class ServiceConfig(Config):
|
|
34
44
|
if 'hostname' in kwargs:
|
35
45
|
self.__hostname = kwargs.get('hostname')
|
36
46
|
|
47
|
+
if 'local' in kwargs:
|
48
|
+
self.__local = kwargs.get('local')
|
49
|
+
|
50
|
+
if 'name' in kwargs:
|
51
|
+
self.__name = kwargs.get('name')
|
52
|
+
elif 'service_name' in kwargs:
|
53
|
+
self.__name = kwargs.get('service_name')
|
54
|
+
|
37
55
|
if 'port' in kwargs:
|
38
56
|
self.__port = kwargs.get('port')
|
39
57
|
|
@@ -46,6 +64,15 @@ class ServiceConfig(Config):
|
|
46
64
|
if 'scheme' in kwargs:
|
47
65
|
self.__scheme = kwargs.get('scheme')
|
48
66
|
|
67
|
+
if 'upstream_hostname' in kwargs:
|
68
|
+
self.__upstream_hostname = kwargs.get('upstream_hostname')
|
69
|
+
|
70
|
+
if 'upstream_port' in kwargs:
|
71
|
+
self.__upstream_port = kwargs.get('upstream_port')
|
72
|
+
|
73
|
+
if 'upstream_scheme' in kwargs:
|
74
|
+
self.__upstream_scheme = kwargs.get('upstream_scheme')
|
75
|
+
|
49
76
|
@property
|
50
77
|
def detached(self) -> bool:
|
51
78
|
return not not self.__detached
|
@@ -66,12 +93,36 @@ class ServiceConfig(Config):
|
|
66
93
|
def hostname(self, v):
|
67
94
|
self.__hostname = v
|
68
95
|
|
96
|
+
@property
|
97
|
+
def local(self):
|
98
|
+
return self.__local
|
99
|
+
|
100
|
+
@local.setter
|
101
|
+
def local(self, v: bool):
|
102
|
+
self.__local = not not v
|
103
|
+
|
104
|
+
@property
|
105
|
+
def name(self):
|
106
|
+
return self.__name
|
107
|
+
|
108
|
+
@name.setter
|
109
|
+
def name(self, v: str):
|
110
|
+
if not v:
|
111
|
+
return
|
112
|
+
|
113
|
+
SERVICE_NAME_PATTERN = re.compile(r'^[a-z0-9]([a-z0-9._-]*[a-z0-9])?$')
|
114
|
+
|
115
|
+
if not bool(SERVICE_NAME_PATTERN.fullmatch(v)):
|
116
|
+
raise ValueError(f"{v} must match {SERVICE_NAME_PATTERN}")
|
117
|
+
|
118
|
+
self.__name = v
|
119
|
+
|
69
120
|
@property
|
70
121
|
def port(self) -> int:
|
71
122
|
if not self.__port:
|
72
|
-
if self.
|
123
|
+
if self.__scheme == 'https':
|
73
124
|
return 443
|
74
|
-
elif self.
|
125
|
+
elif self.__scheme == 'http':
|
75
126
|
return 80
|
76
127
|
|
77
128
|
return self.__port
|
@@ -83,6 +134,8 @@ class ServiceConfig(Config):
|
|
83
134
|
else:
|
84
135
|
v = int(v)
|
85
136
|
if v > 0 and v <= 65535:
|
137
|
+
if v == self.__upstream_port and self.local:
|
138
|
+
raise ValueError('port cannot be the same as upstream port')
|
86
139
|
self.__port = v
|
87
140
|
|
88
141
|
@property
|
@@ -107,57 +160,112 @@ class ServiceConfig(Config):
|
|
107
160
|
if not self.hostname:
|
108
161
|
return ''
|
109
162
|
|
110
|
-
return
|
111
|
-
|
112
|
-
@property
|
113
|
-
def url(self):
|
114
|
-
_url = f"{self.scheme}://{self.hostname}"
|
115
|
-
|
116
|
-
if not self.port:
|
117
|
-
return _url
|
118
|
-
|
119
|
-
if self.scheme == 'http' and self.port == 80:
|
120
|
-
return _url
|
121
|
-
|
122
|
-
if self.scheme == 'https' and self.port == 443:
|
123
|
-
return _url
|
124
|
-
|
125
|
-
return f"{_url}:{self.port}"
|
163
|
+
return 'regular'
|
126
164
|
|
127
165
|
@proxy_mode.setter
|
128
|
-
def proxy_mode(self, v):
|
166
|
+
def proxy_mode(self, v: str):
|
167
|
+
if v and v not in ['regular', 'reverse']:
|
168
|
+
v = 'reverse'
|
129
169
|
self.__proxy_mode = v
|
130
170
|
|
131
171
|
@property
|
132
172
|
def scheme(self):
|
173
|
+
if not self.__scheme and self.__port:
|
174
|
+
if self.port == 443:
|
175
|
+
return 'https'
|
176
|
+
else:
|
177
|
+
return 'http'
|
178
|
+
|
133
179
|
return (self.__scheme or 'https').strip()
|
134
180
|
|
135
181
|
@scheme.setter
|
136
182
|
def scheme(self, v):
|
183
|
+
if v and not v in ['http', 'https']:
|
184
|
+
raise ValueError('scheme must be http or https')
|
137
185
|
self.__scheme = v
|
138
186
|
|
139
187
|
@property
|
140
188
|
def tls(self) -> bool:
|
141
189
|
return self.__scheme == 'https'
|
142
190
|
|
191
|
+
@property
|
192
|
+
def upstream_hostname(self) -> int:
|
193
|
+
if self.local:
|
194
|
+
return 'host.docker.internal'
|
195
|
+
return self.__upstream_hostname or self.hostname
|
196
|
+
|
197
|
+
@upstream_hostname.setter
|
198
|
+
def upstream_hostname(self, v: str):
|
199
|
+
self.__upstream_hostname = v
|
200
|
+
|
201
|
+
@property
|
202
|
+
def upstream_port(self) -> int:
|
203
|
+
return self.__upstream_port or self.port
|
204
|
+
|
205
|
+
@upstream_port.setter
|
206
|
+
def upstream_port(self, v: int):
|
207
|
+
if v == None:
|
208
|
+
self.__upstream_port = v
|
209
|
+
else:
|
210
|
+
v = int(v)
|
211
|
+
if v > 0 and v <= 65535:
|
212
|
+
if v == self.__port and self.local:
|
213
|
+
raise ValueError('upstream port cannot be the same as port')
|
214
|
+
self.__upstream_port = v
|
215
|
+
else:
|
216
|
+
raise ValueError('upstream port must be between 0 and 65535')
|
217
|
+
|
218
|
+
@property
|
219
|
+
def upstream_scheme(self):
|
220
|
+
return self.__upstream_scheme or self.scheme
|
221
|
+
|
222
|
+
@upstream_scheme.setter
|
223
|
+
def upstream_scheme(self, v):
|
224
|
+
self.__scheme = v
|
225
|
+
|
226
|
+
@property
|
227
|
+
def url(self):
|
228
|
+
_url = f"{self.scheme}://{self.hostname}"
|
229
|
+
|
230
|
+
if not self.port:
|
231
|
+
return _url
|
232
|
+
|
233
|
+
if self.scheme == 'http' and self.port == 80:
|
234
|
+
return _url
|
235
|
+
|
236
|
+
if self.scheme == 'https' and self.port == 443:
|
237
|
+
return _url
|
238
|
+
|
239
|
+
return f"{_url}:{self.port}"
|
240
|
+
|
143
241
|
def load(self, config = None):
|
144
242
|
config = config or self.read()
|
145
243
|
|
146
244
|
self.detached = config.get(SERVICE_DETACHED_ENV)
|
147
245
|
self.hostname = config.get(SERVICE_HOSTNAME_ENV)
|
246
|
+
self.local = config.get(SERVICE_LOCAL_ENV)
|
247
|
+
self.name = config.get(SERVICE_NAME_ENV)
|
148
248
|
self.port = config.get(SERVICE_PORT_ENV)
|
149
249
|
self.priority = config.get(SERVICE_PRIORITY_ENV)
|
150
250
|
self.proxy_mode = config.get(SERVICE_PROXY_MODE_ENV)
|
151
251
|
self.scheme = config.get(SERVICE_SCHEME_ENV)
|
252
|
+
self.upstream_hostname = config.get(SERVICE_UPSTREAM_HOSTNAME_ENV)
|
253
|
+
self.upstream_port = config.get(SERVICE_UPSTREAM_PORT_ENV)
|
254
|
+
self.upstream_scheme = config.get(SERVICE_UPSTREAM_SCHEME_ENV)
|
152
255
|
|
153
256
|
def to_dict(self):
|
154
257
|
return {
|
155
258
|
'detached': self.detached,
|
156
259
|
'hostname': self.hostname,
|
260
|
+
'local': self.local,
|
261
|
+
'name': self.name,
|
157
262
|
'port': self.port,
|
158
263
|
'priority': self.priority,
|
159
264
|
'proxy_mode': self.proxy_mode,
|
160
265
|
'scheme': self.scheme if self.hostname else '',
|
266
|
+
'upstream_hostname': self.upstream_hostname,
|
267
|
+
'upstream_port': self.upstream_port,
|
268
|
+
'upstream_scheme': self.upstream_scheme,
|
161
269
|
}
|
162
270
|
|
163
271
|
def write(self):
|
@@ -166,6 +274,12 @@ class ServiceConfig(Config):
|
|
166
274
|
if self.hostname:
|
167
275
|
config[SERVICE_HOSTNAME_ENV] = self.hostname
|
168
276
|
|
277
|
+
if self.local:
|
278
|
+
config[SERVICE_LOCAL_ENV] = True
|
279
|
+
|
280
|
+
if self.name:
|
281
|
+
config[SERVICE_NAME_ENV] = self.name
|
282
|
+
|
169
283
|
if self.port:
|
170
284
|
config[SERVICE_PORT_ENV] = self.port
|
171
285
|
|
@@ -175,6 +289,15 @@ class ServiceConfig(Config):
|
|
175
289
|
if self.scheme:
|
176
290
|
config[SERVICE_SCHEME_ENV] = self.scheme
|
177
291
|
|
292
|
+
if self.upstream_hostname:
|
293
|
+
config[SERVICE_UPSTREAM_HOSTNAME_ENV] = self.upstream_hostname
|
294
|
+
|
295
|
+
if self.upstream_port:
|
296
|
+
config[SERVICE_UPSTREAM_PORT_ENV] = self.upstream_port
|
297
|
+
|
298
|
+
if self.upstream_scheme:
|
299
|
+
config[SERVICE_UPSTREAM_SCHEME_ENV] = self.upstream_scheme
|
300
|
+
|
178
301
|
config[SERVICE_DETACHED_ENV] = bool(self.detached)
|
179
302
|
config[SERVICE_ID_ENV] = self.id
|
180
303
|
config[SERVICE_PROXY_MODE_ENV] = self.proxy_mode
|
@@ -2,6 +2,8 @@ import os
|
|
2
2
|
import pdb
|
3
3
|
import shutil
|
4
4
|
|
5
|
+
from copy import deepcopy
|
6
|
+
|
5
7
|
from .app import App
|
6
8
|
from .constants import WORKFLOW_MOCK_TYPE, WORKFLOW_RECORD_TYPE, WORKFLOW_TEST_TYPE
|
7
9
|
from .docker.service.builder import ServiceBuilder
|
@@ -14,8 +16,13 @@ class ServiceCreateCommand(ServiceCommand):
|
|
14
16
|
def __init__(self, app: App, **kwargs):
|
15
17
|
super().__init__(app, **kwargs)
|
16
18
|
|
19
|
+
self.__upstream_port = kwargs.get('upstream_port') or []
|
17
20
|
self.__env_vars = kwargs.get('env') or []
|
18
|
-
self.__workflows = kwargs.get('workflow') or []
|
21
|
+
self.__workflows = kwargs.get('workflow') or [WORKFLOW_RECORD_TYPE, WORKFLOW_MOCK_TYPE, WORKFLOW_TEST_TYPE]
|
22
|
+
|
23
|
+
@property
|
24
|
+
def upstream_port(self):
|
25
|
+
return self.__upstream_port
|
19
26
|
|
20
27
|
@property
|
21
28
|
def env_vars(self):
|
@@ -45,7 +52,9 @@ class ServiceCreateCommand(ServiceCommand):
|
|
45
52
|
self.__build_with_mock_workflow(service_builder, **workflow_kwargs)
|
46
53
|
|
47
54
|
if WORKFLOW_RECORD_TYPE in self.workflows:
|
48
|
-
|
55
|
+
service_builder_copy = deepcopy(service_builder)
|
56
|
+
service_builder_copy.with_upstream_port(self.upstream_port)
|
57
|
+
self.__build_with_record_workflow(service_builder_copy, **workflow_kwargs)
|
49
58
|
|
50
59
|
if WORKFLOW_TEST_TYPE in self.workflows:
|
51
60
|
self.__build_with_test_workflow(service_builder, **workflow_kwargs)
|
@@ -0,0 +1,51 @@
|
|
1
|
+
import pdb
|
2
|
+
import re
|
3
|
+
|
4
|
+
from typing import Optional
|
5
|
+
|
6
|
+
class ServiceDependency():
|
7
|
+
def __init__(self, s: str):
|
8
|
+
parts = s.rsplit(":", 2) # split from the right, at most twice
|
9
|
+
if len(parts) != 3:
|
10
|
+
raise ValueError(f"Invalid format: {s}. Expected 'IMAGE:HOSTNAME:PORT'")
|
11
|
+
|
12
|
+
self.image, self.hostname, self.port = parts
|
13
|
+
|
14
|
+
# Basic validation
|
15
|
+
if not self.image:
|
16
|
+
raise ValueError("Image name cannot be empty")
|
17
|
+
if not self.hostname:
|
18
|
+
raise ValueError("Hostname cannot be empty")
|
19
|
+
if not self.port.isdigit():
|
20
|
+
raise ValueError(f"Port must be a number, got: {self.port}")
|
21
|
+
|
22
|
+
self.port = int(self.port)
|
23
|
+
|
24
|
+
def __repr__(self):
|
25
|
+
return f"ImageHostPort(image='{self.image}', hostname='{self.hostname}', port={self.port})"
|
26
|
+
|
27
|
+
@property
|
28
|
+
def image_name(self):
|
29
|
+
return DockerImage(self.image).name
|
30
|
+
|
31
|
+
class DockerImage:
|
32
|
+
def __init__(self, ref: str):
|
33
|
+
# Regex based on Docker image spec
|
34
|
+
pattern = r"""
|
35
|
+
^(?:(?P<registry>[^/]+?)/)? # optional registry (no slash)
|
36
|
+
(?P<name>[^:@]+(?:/[^:@]+)*) # image name (can have slashes)
|
37
|
+
(?::(?P<tag>[^@]+))? # optional :tag
|
38
|
+
(?:@(?P<digest>.+))? # optional @digest
|
39
|
+
"""
|
40
|
+
match = re.match(pattern, ref, re.VERBOSE)
|
41
|
+
if not match:
|
42
|
+
raise ValueError(f"Invalid Docker image reference: {ref}")
|
43
|
+
|
44
|
+
self.registry: Optional[str] = match.group("registry")
|
45
|
+
self.name: str = match.group("name")
|
46
|
+
self.tag: Optional[str] = match.group("tag")
|
47
|
+
self.digest: Optional[str] = match.group("digest")
|
48
|
+
|
49
|
+
def __repr__(self):
|
50
|
+
return (f"DockerImage(registry={self.registry!r}, "
|
51
|
+
f"name={self.name!r}, tag={self.tag!r}, digest={self.digest!r})")
|
@@ -1,7 +1,17 @@
|
|
1
|
-
# Define services here
|
1
|
+
# Define custom services here
|
2
2
|
#
|
3
|
-
#
|
4
|
-
#
|
5
|
-
#
|
6
|
-
#
|
7
|
-
|
3
|
+
# 1. Uncomment the following
|
4
|
+
# 2. Replace <SERVICE-NAME> with the name of the service, this can be found in ../config.yml
|
5
|
+
# 3. Extend as needed
|
6
|
+
#
|
7
|
+
# To learn more, see https://docs.stoobly.com/core-concepts/scaffold
|
8
|
+
|
9
|
+
#services:
|
10
|
+
# <SERVICE-NAME>.<CUSTOM-SERVICE-NAME>:
|
11
|
+
# depends_on:
|
12
|
+
# <SERVICE-NAME>.configure:
|
13
|
+
# condition: service_completed_successfully
|
14
|
+
# networks:
|
15
|
+
# app.ingress: {}
|
16
|
+
# profiles:
|
17
|
+
# - ${WORKFLOW_NAME}
|
@@ -1,7 +1,17 @@
|
|
1
|
-
# Define services here
|
1
|
+
# Define custom services here
|
2
2
|
#
|
3
|
-
#
|
4
|
-
#
|
5
|
-
#
|
6
|
-
#
|
7
|
-
|
3
|
+
# 1. Uncomment the following
|
4
|
+
# 2. Replace <SERVICE-NAME> with the name of the service, this can be found in ../config.yml
|
5
|
+
# 3. Extend as needed
|
6
|
+
#
|
7
|
+
# To learn more, see https://docs.stoobly.com/core-concepts/scaffold
|
8
|
+
|
9
|
+
#services:
|
10
|
+
# <SERVICE-NAME>.<CUSTOM-SERVICE-NAME>:
|
11
|
+
# depends_on:
|
12
|
+
# <SERVICE-NAME>.configure:
|
13
|
+
# condition: service_completed_successfully
|
14
|
+
# networks:
|
15
|
+
# app.egress: {}
|
16
|
+
# profiles:
|
17
|
+
# - ${WORKFLOW_NAME}
|
@@ -1,7 +1,17 @@
|
|
1
|
-
# Define services here
|
1
|
+
# Define custom services here
|
2
2
|
#
|
3
|
-
#
|
4
|
-
#
|
5
|
-
#
|
6
|
-
#
|
7
|
-
|
3
|
+
# 1. Uncomment the following
|
4
|
+
# 2. Replace <SERVICE-NAME> with the name of the service, this can be found in ../config.yml
|
5
|
+
# 3. Extend as needed
|
6
|
+
#
|
7
|
+
# To learn more, see https://docs.stoobly.com/core-concepts/scaffold
|
8
|
+
|
9
|
+
#services:
|
10
|
+
# <SERVICE-NAME>.<CUSTOM-SERVICE-NAME>:
|
11
|
+
# depends_on:
|
12
|
+
# <SERVICE-NAME>.configure:
|
13
|
+
# condition: service_completed_successfully
|
14
|
+
# networks:
|
15
|
+
# app.ingress: {}
|
16
|
+
# profiles:
|
17
|
+
# - ${WORKFLOW_NAME}
|
@@ -1,11 +1,17 @@
|
|
1
|
-
# Define services here
|
1
|
+
# Define custom services here
|
2
2
|
#
|
3
|
-
#
|
4
|
-
#
|
5
|
-
#
|
6
|
-
#
|
7
|
-
#
|
8
|
-
|
9
|
-
#
|
10
|
-
#
|
11
|
-
|
3
|
+
# 1. Uncomment the following
|
4
|
+
# 2. Replace <SERVICE-NAME> with the name of the service, this can be found in ../config.yml
|
5
|
+
# 3. Extend as needed
|
6
|
+
#
|
7
|
+
# To learn more, see https://docs.stoobly.com/core-concepts/scaffold
|
8
|
+
|
9
|
+
#services:
|
10
|
+
# <SERVICE-NAME>.<CUSTOM-SERVICE-NAME>:
|
11
|
+
# depends_on:
|
12
|
+
# <SERVICE-NAME>.configure:
|
13
|
+
# condition: service_completed_successfully
|
14
|
+
# networks:
|
15
|
+
# app.ingress: {}
|
16
|
+
# profiles:
|
17
|
+
# - ${WORKFLOW_NAME}
|
@@ -1,11 +1,17 @@
|
|
1
|
-
# Define services here
|
1
|
+
# Define custom services here
|
2
2
|
#
|
3
|
-
#
|
4
|
-
#
|
5
|
-
#
|
6
|
-
#
|
7
|
-
#
|
8
|
-
|
9
|
-
#
|
10
|
-
#
|
11
|
-
|
3
|
+
# 1. Uncomment the following
|
4
|
+
# 2. Replace <SERVICE-NAME> with the name of the service, this can be found in ../config.yml
|
5
|
+
# 3. Extend as needed
|
6
|
+
#
|
7
|
+
# To learn more, see https://docs.stoobly.com/core-concepts/scaffold
|
8
|
+
|
9
|
+
#services:
|
10
|
+
# <SERVICE-NAME>.<CUSTOM-SERVICE-NAME>:
|
11
|
+
# depends_on:
|
12
|
+
# <SERVICE-NAME>.configure:
|
13
|
+
# condition: service_completed_successfully
|
14
|
+
# networks:
|
15
|
+
# app.egress: {}
|
16
|
+
# profiles:
|
17
|
+
# - ${WORKFLOW_NAME}
|
@@ -1,11 +1,17 @@
|
|
1
|
-
# Define services here
|
1
|
+
# Define custom services here
|
2
2
|
#
|
3
|
-
#
|
4
|
-
#
|
5
|
-
#
|
6
|
-
#
|
7
|
-
#
|
8
|
-
|
9
|
-
#
|
10
|
-
#
|
11
|
-
|
3
|
+
# 1. Uncomment the following
|
4
|
+
# 2. Replace <SERVICE-NAME> with the name of the service, this can be found in ../config.yml
|
5
|
+
# 3. Extend as needed
|
6
|
+
#
|
7
|
+
# To learn more, see https://docs.stoobly.com/core-concepts/scaffold
|
8
|
+
|
9
|
+
#services:
|
10
|
+
# <SERVICE-NAME>.<CUSTOM-SERVICE-NAME>:
|
11
|
+
# depends_on:
|
12
|
+
# <SERVICE-NAME>.configure:
|
13
|
+
# condition: service_completed_successfully
|
14
|
+
# networks:
|
15
|
+
# app.ingress: {}
|
16
|
+
# profiles:
|
17
|
+
# - ${WORKFLOW_NAME}
|
@@ -1,11 +1,14 @@
|
|
1
1
|
services:
|
2
2
|
stoobly_ui.service:
|
3
|
-
command: --ui-port 4200
|
3
|
+
command: --proxyless --ui-host local.stoobly.com --ui-port 4200
|
4
4
|
extends:
|
5
5
|
file: ../.docker-compose.base.yml
|
6
6
|
service: stoobly_ui.base
|
7
|
-
|
7
|
+
networks:
|
8
|
+
app.egress:
|
9
|
+
aliases:
|
10
|
+
- local.stoobly.com
|
8
11
|
ports:
|
9
12
|
- '${APP_UI_PORT}:4200'
|
10
13
|
profiles:
|
11
|
-
-
|
14
|
+
- ${WORKFLOW_NAME}
|
@@ -1,12 +1,14 @@
|
|
1
1
|
services:
|
2
2
|
stoobly_ui.service:
|
3
|
-
command: --ui-port 4200
|
3
|
+
command: --proxyless --ui-host local.stoobly.com --ui-port 4200
|
4
4
|
extends:
|
5
5
|
file: ../.docker-compose.base.yml
|
6
6
|
service: stoobly_ui.base
|
7
|
-
|
7
|
+
networks:
|
8
|
+
app.egress:
|
9
|
+
aliases:
|
10
|
+
- local.stoobly.com
|
8
11
|
ports:
|
9
12
|
- '${APP_UI_PORT}:4200'
|
10
13
|
profiles:
|
11
|
-
-
|
12
|
-
|
14
|
+
- ${WORKFLOW_NAME}
|