stoobly-agent 1.10.1__py3-none-any.whl → 1.10.2__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/__main__.py +10 -0
- stoobly_agent/app/cli/ca_cert_cli.py +9 -5
- stoobly_agent/app/cli/helpers/replay_facade.py +2 -2
- stoobly_agent/app/cli/intercept_cli.py +5 -5
- stoobly_agent/app/cli/request_cli.py +2 -2
- stoobly_agent/app/cli/scaffold/app.py +14 -5
- stoobly_agent/app/cli/scaffold/app_command.py +0 -4
- stoobly_agent/app/cli/scaffold/app_config.py +49 -2
- stoobly_agent/app/cli/scaffold/app_create_command.py +145 -76
- stoobly_agent/app/cli/scaffold/constants.py +8 -1
- stoobly_agent/app/cli/scaffold/docker/constants.py +3 -1
- stoobly_agent/app/cli/scaffold/docker/service/build_decorator.py +2 -2
- stoobly_agent/app/cli/scaffold/docker/service/builder.py +15 -49
- stoobly_agent/app/cli/scaffold/docker/service/configure_gateway.py +3 -0
- stoobly_agent/app/cli/scaffold/docker/template_files.py +112 -0
- stoobly_agent/app/cli/scaffold/docker/workflow/build_decorator.py +1 -1
- stoobly_agent/app/cli/scaffold/docker/workflow/builder.py +31 -39
- stoobly_agent/app/cli/scaffold/docker/workflow/command_decorator.py +1 -1
- stoobly_agent/app/cli/scaffold/docker/workflow/detached_decorator.py +1 -1
- stoobly_agent/app/cli/scaffold/docker/workflow/dns_decorator.py +2 -3
- stoobly_agent/app/cli/scaffold/docker/workflow/local_decorator.py +1 -1
- stoobly_agent/app/cli/scaffold/docker/workflow/mock_decorator.py +1 -1
- stoobly_agent/app/cli/scaffold/docker/workflow/reverse_proxy_decorator.py +1 -1
- stoobly_agent/app/cli/scaffold/docker/workflow/run_command.py +423 -0
- stoobly_agent/app/cli/scaffold/local/__init__.py +0 -0
- stoobly_agent/app/cli/scaffold/local/service/__init__.py +0 -0
- stoobly_agent/app/cli/scaffold/local/service/builder.py +72 -0
- stoobly_agent/app/cli/scaffold/local/workflow/__init__.py +0 -0
- stoobly_agent/app/cli/scaffold/local/workflow/builder.py +35 -0
- stoobly_agent/app/cli/scaffold/local/workflow/run_command.py +339 -0
- stoobly_agent/app/cli/scaffold/service_command.py +9 -1
- stoobly_agent/app/cli/scaffold/service_config.py +8 -0
- stoobly_agent/app/cli/scaffold/service_create_command.py +18 -6
- stoobly_agent/app/cli/scaffold/service_docker_compose.py +1 -1
- stoobly_agent/app/cli/scaffold/templates/app/.Makefile +2 -2
- stoobly_agent/app/cli/scaffold/templates/app/build/.docker-compose.base.yml +2 -2
- stoobly_agent/app/cli/scaffold/templates/app/entrypoint/.docker-compose.base.yml +2 -2
- stoobly_agent/app/cli/scaffold/templates/app/entrypoint/mock/run +3 -0
- stoobly_agent/app/cli/scaffold/templates/app/entrypoint/record/run +3 -0
- stoobly_agent/app/cli/scaffold/templates/app/entrypoint/test/run +3 -0
- stoobly_agent/app/cli/scaffold/templates/build/services/build/mock/.configure +5 -1
- stoobly_agent/app/cli/scaffold/templates/build/services/build/mock/.init +5 -1
- stoobly_agent/app/cli/scaffold/templates/build/services/build/mock/.run +14 -0
- stoobly_agent/app/cli/scaffold/templates/build/services/build/record/.configure +5 -1
- stoobly_agent/app/cli/scaffold/templates/build/services/build/record/.init +5 -1
- stoobly_agent/app/cli/scaffold/templates/build/services/build/record/.run +14 -0
- stoobly_agent/app/cli/scaffold/templates/build/services/build/test/.configure +5 -1
- stoobly_agent/app/cli/scaffold/templates/build/services/build/test/.init +5 -1
- stoobly_agent/app/cli/scaffold/templates/build/services/build/test/.run +14 -0
- stoobly_agent/app/cli/scaffold/templates/build/services/entrypoint/mock/.configure +5 -1
- stoobly_agent/app/cli/scaffold/templates/build/services/entrypoint/mock/.init +5 -1
- stoobly_agent/app/cli/scaffold/templates/build/services/entrypoint/mock/.run +19 -0
- stoobly_agent/app/cli/scaffold/templates/build/services/entrypoint/record/.configure +5 -1
- stoobly_agent/app/cli/scaffold/templates/build/services/entrypoint/record/.init +5 -1
- stoobly_agent/app/cli/scaffold/templates/build/services/entrypoint/record/.run +19 -0
- stoobly_agent/app/cli/scaffold/templates/build/services/entrypoint/test/.configure +5 -1
- stoobly_agent/app/cli/scaffold/templates/build/services/entrypoint/test/.init +5 -1
- stoobly_agent/app/cli/scaffold/templates/build/services/entrypoint/test/.run +19 -0
- stoobly_agent/app/cli/scaffold/templates/build/workflows/exec/scaffold/.up +0 -1
- stoobly_agent/app/cli/scaffold/templates/build/workflows/mock/.configure +5 -1
- stoobly_agent/app/cli/scaffold/templates/build/workflows/mock/.init +5 -1
- stoobly_agent/app/cli/scaffold/templates/build/workflows/mock/.run +14 -0
- stoobly_agent/app/cli/scaffold/templates/build/workflows/record/.configure +5 -1
- stoobly_agent/app/cli/scaffold/templates/build/workflows/record/.init +5 -1
- stoobly_agent/app/cli/scaffold/templates/build/workflows/record/.run +14 -0
- stoobly_agent/app/cli/scaffold/templates/build/workflows/test/.configure +5 -1
- stoobly_agent/app/cli/scaffold/templates/build/workflows/test/.init +5 -1
- stoobly_agent/app/cli/scaffold/templates/build/workflows/test/.run +14 -0
- stoobly_agent/app/cli/scaffold/templates/constants.py +35 -19
- stoobly_agent/app/cli/scaffold/templates/factory.py +34 -18
- stoobly_agent/app/cli/scaffold/templates/plugins/cypress/test/.run +21 -0
- stoobly_agent/app/cli/scaffold/templates/plugins/playwright/test/.run +21 -0
- stoobly_agent/app/cli/scaffold/templates/workflow/mock/run +3 -0
- stoobly_agent/app/cli/scaffold/templates/workflow/record/run +3 -0
- stoobly_agent/app/cli/scaffold/templates/workflow/test/run +3 -0
- stoobly_agent/app/cli/scaffold/workflow_command.py +18 -4
- stoobly_agent/app/cli/scaffold/workflow_copy_command.py +5 -4
- stoobly_agent/app/cli/scaffold/workflow_create_command.py +31 -29
- stoobly_agent/app/cli/scaffold/workflow_run_command.py +18 -151
- stoobly_agent/app/cli/scaffold_cli.py +115 -161
- stoobly_agent/app/cli/scenario_cli.py +2 -2
- stoobly_agent/app/cli/types/test.py +2 -2
- stoobly_agent/app/cli/types/workflow_run_command.py +52 -3
- stoobly_agent/app/proxy/handle_mock_service.py +1 -1
- stoobly_agent/app/proxy/intercept_settings.py +5 -25
- stoobly_agent/app/proxy/mock/eval_fixtures_service.py +177 -27
- stoobly_agent/app/proxy/mock/types/__init__.py +22 -1
- stoobly_agent/app/proxy/replay/body_parser_service.py +8 -5
- stoobly_agent/app/proxy/replay/multipart.py +15 -13
- stoobly_agent/app/proxy/replay/replay_request_service.py +2 -2
- stoobly_agent/app/proxy/run.py +3 -0
- stoobly_agent/app/proxy/test/context.py +0 -4
- stoobly_agent/app/proxy/test/context_abc.py +0 -5
- stoobly_agent/cli.py +61 -16
- stoobly_agent/config/data_dir.py +0 -8
- stoobly_agent/public/12-es2015.618ecfd5f735b801b50f.js +1 -0
- stoobly_agent/public/12-es5.618ecfd5f735b801b50f.js +1 -0
- stoobly_agent/public/index.html +1 -1
- stoobly_agent/public/runtime-es2015.77bcd31efed9e5d5d431.js +1 -0
- stoobly_agent/public/runtime-es5.77bcd31efed9e5d5d431.js +1 -0
- stoobly_agent/test/app/cli/intercept/intercept_configure_test.py +17 -6
- stoobly_agent/test/app/cli/scaffold/docker/cli_invoker.py +177 -0
- stoobly_agent/test/app/cli/scaffold/{cli_test.py → docker/cli_test.py} +1 -8
- stoobly_agent/test/app/cli/scaffold/{e2e_test.py → docker/e2e_test.py} +31 -16
- stoobly_agent/test/app/cli/scaffold/local/__init__.py +0 -0
- stoobly_agent/test/app/cli/scaffold/{cli_invoker.py → local/cli_invoker.py} +38 -32
- stoobly_agent/test/app/cli/scaffold/local/e2e_test.py +342 -0
- stoobly_agent/test/app/models/schemas/.stoobly/db/VERSION +1 -1
- stoobly_agent/test/app/proxy/mock/eval_fixtures_service_test.py +903 -2
- stoobly_agent/test/app/proxy/replay/body_parser_service_test.py +95 -3
- stoobly_agent/test/config/data_dir_test.py +2 -7
- stoobly_agent/test/test_helper.py +16 -5
- {stoobly_agent-1.10.1.dist-info → stoobly_agent-1.10.2.dist-info}/METADATA +4 -2
- {stoobly_agent-1.10.1.dist-info → stoobly_agent-1.10.2.dist-info}/RECORD +150 -122
- {stoobly_agent-1.10.1.dist-info → stoobly_agent-1.10.2.dist-info}/WHEEL +1 -1
- stoobly_agent/app/cli/helpers/shell.py +0 -26
- stoobly_agent/public/12-es2015.be58ed0ef449008b932e.js +0 -1
- stoobly_agent/public/12-es5.be58ed0ef449008b932e.js +0 -1
- stoobly_agent/public/runtime-es2015.f8c814b38b27708e91c1.js +0 -1
- stoobly_agent/public/runtime-es5.f8c814b38b27708e91c1.js +0 -1
- /stoobly_agent/app/cli/scaffold/templates/app/build/mock/{.docker-compose.mock.yml → .docker-compose.yml} +0 -0
- /stoobly_agent/app/cli/scaffold/templates/app/build/mock/{bin/configure → configure} +0 -0
- /stoobly_agent/app/cli/scaffold/templates/app/build/mock/{bin/init → init} +0 -0
- /stoobly_agent/app/cli/scaffold/templates/app/build/record/{.docker-compose.record.yml → .docker-compose.yml} +0 -0
- /stoobly_agent/app/cli/scaffold/templates/app/build/record/{bin/configure → configure} +0 -0
- /stoobly_agent/app/cli/scaffold/templates/app/build/record/{bin/init → init} +0 -0
- /stoobly_agent/app/cli/scaffold/templates/app/build/test/{.docker-compose.test.yml → .docker-compose.yml} +0 -0
- /stoobly_agent/app/cli/scaffold/templates/app/build/test/{bin/configure → configure} +0 -0
- /stoobly_agent/app/cli/scaffold/templates/app/build/test/{bin/init → init} +0 -0
- /stoobly_agent/app/cli/scaffold/templates/app/entrypoint/mock/{.docker-compose.mock.yml → .docker-compose.yml} +0 -0
- /stoobly_agent/app/cli/scaffold/templates/app/entrypoint/mock/{bin/configure → configure} +0 -0
- /stoobly_agent/app/cli/scaffold/templates/app/entrypoint/mock/{bin/init → init} +0 -0
- /stoobly_agent/app/cli/scaffold/templates/app/entrypoint/record/{.docker-compose.record.yml → .docker-compose.yml} +0 -0
- /stoobly_agent/app/cli/scaffold/templates/app/entrypoint/record/{bin/configure → configure} +0 -0
- /stoobly_agent/app/cli/scaffold/templates/app/entrypoint/record/{bin/init → init} +0 -0
- /stoobly_agent/app/cli/scaffold/templates/app/entrypoint/test/{.docker-compose.test.yml → .docker-compose.yml} +0 -0
- /stoobly_agent/app/cli/scaffold/templates/app/entrypoint/test/{bin/configure → configure} +0 -0
- /stoobly_agent/app/cli/scaffold/templates/app/entrypoint/test/{bin/init → init} +0 -0
- /stoobly_agent/app/cli/scaffold/templates/app/gateway/mock/{.docker-compose.mock.yml → .docker-compose.yml} +0 -0
- /stoobly_agent/app/cli/scaffold/templates/app/gateway/record/{.docker-compose.record.yml → .docker-compose.yml} +0 -0
- /stoobly_agent/app/cli/scaffold/templates/app/gateway/test/{.docker-compose.test.yml → .docker-compose.yml} +0 -0
- /stoobly_agent/app/cli/scaffold/templates/app/stoobly-ui/exec/{.docker-compose.exec.yml → .docker-compose.yml} +0 -0
- /stoobly_agent/app/cli/scaffold/templates/app/stoobly-ui/mock/{.docker-compose.mock.yml → .docker-compose.yml} +0 -0
- /stoobly_agent/app/cli/scaffold/templates/app/stoobly-ui/record/{.docker-compose.record.yml → .docker-compose.yml} +0 -0
- /stoobly_agent/app/cli/scaffold/templates/plugins/cypress/test/{.docker-compose.test.yml → .docker-compose.yml} +0 -0
- /stoobly_agent/app/cli/scaffold/templates/plugins/playwright/test/{.docker-compose.test.yml → .docker-compose.yml} +0 -0
- /stoobly_agent/app/cli/scaffold/templates/workflow/mock/{bin/configure → configure} +0 -0
- /stoobly_agent/app/cli/scaffold/templates/workflow/mock/{bin/init → init} +0 -0
- /stoobly_agent/app/cli/scaffold/templates/workflow/record/{bin/configure → configure} +0 -0
- /stoobly_agent/app/cli/scaffold/templates/workflow/record/{bin/init → init} +0 -0
- /stoobly_agent/app/cli/scaffold/templates/workflow/test/{bin/configure → configure} +0 -0
- /stoobly_agent/app/cli/scaffold/templates/workflow/test/{bin/init → init} +0 -0
- {stoobly_agent-1.10.1.dist-info → stoobly_agent-1.10.2.dist-info}/entry_points.txt +0 -0
- {stoobly_agent-1.10.1.dist-info → stoobly_agent-1.10.2.dist-info/licenses}/LICENSE +0 -0
stoobly_agent/__init__.py
CHANGED
@@ -1,2 +1,2 @@
|
|
1
1
|
COMMAND = 'stoobly-agent'
|
2
|
-
VERSION = '1.10.
|
2
|
+
VERSION = '1.10.2'
|
@@ -48,14 +48,18 @@ def mkcert(**kwargs):
|
|
48
48
|
)
|
49
49
|
def install(**kwargs):
|
50
50
|
ca_certs_dir_path = kwargs['ca_certs_dir_path']
|
51
|
+
|
52
|
+
ca_cert_install(ca_certs_dir_path)
|
53
|
+
|
54
|
+
@ca_cert.command()
|
55
|
+
def uninstall():
|
56
|
+
print("Not yet implemented. Stay tuned!")
|
57
|
+
|
58
|
+
def ca_cert_install(ca_certs_dir_path: str):
|
51
59
|
installer = CertificateAuthority(ca_certs_dir_path)
|
52
60
|
|
53
61
|
try:
|
54
62
|
installer.install()
|
55
63
|
except Exception as e:
|
56
64
|
print(e, file=sys.stderr)
|
57
|
-
sys.exit(1)
|
58
|
-
|
59
|
-
@ca_cert.command()
|
60
|
-
def uninstall():
|
61
|
-
print("Not yet implemented. Stay tuned!")
|
65
|
+
sys.exit(1)
|
@@ -17,9 +17,9 @@ class ReplayCliOptions(TypedDict):
|
|
17
17
|
lifecycle_hooks_path: str
|
18
18
|
on_response: Callable
|
19
19
|
project_key: str
|
20
|
-
public_directory_path: str
|
20
|
+
public_directory_path: str # Comma-separated list of paths, optionally with origin prefix
|
21
21
|
record: bool
|
22
|
-
response_fixtures_path: str
|
22
|
+
response_fixtures_path: str # Comma-separated list of paths, optionally with origin prefix
|
23
23
|
scenario_key: str
|
24
24
|
scheme: str
|
25
25
|
trace: Trace
|
@@ -10,7 +10,7 @@ from .helpers.handle_config_update_service import handle_intercept_active_update
|
|
10
10
|
|
11
11
|
settings: Settings = Settings.instance()
|
12
12
|
|
13
|
-
mode_options = [mode.MOCK, mode.RECORD, mode.REPLAY
|
13
|
+
mode_options = [mode.MOCK, mode.RECORD, mode.REPLAY]
|
14
14
|
|
15
15
|
if settings.cli.features.remote:
|
16
16
|
mode_options.append(mode.TEST)
|
@@ -87,9 +87,9 @@ def disable(**kwargs):
|
|
87
87
|
help="Configure intercept"
|
88
88
|
)
|
89
89
|
@click.option('--mode', type=click.Choice(mode_options))
|
90
|
-
@click.option('--order',
|
91
|
-
@click.option('--policy',
|
92
|
-
@click.option('--strategy',
|
90
|
+
@click.option('--order', help=f"Order to use for recording. Valid options: {order_options}")
|
91
|
+
@click.option('--policy', help=f"Policy to use for recording. Valid options: {policy_options}")
|
92
|
+
@click.option('--strategy', help=f"Strategy to use for recording. Valid options: {strategy_options}")
|
93
93
|
def configure(**kwargs):
|
94
94
|
settings: Settings = Settings.instance()
|
95
95
|
|
@@ -172,7 +172,7 @@ def configure(**kwargs):
|
|
172
172
|
help="Show intercept"
|
173
173
|
)
|
174
174
|
def show(**kwargs):
|
175
|
-
settings = Settings.instance()
|
175
|
+
settings: Settings = Settings.instance()
|
176
176
|
|
177
177
|
_mode = settings.proxy.intercept.mode
|
178
178
|
project_key = ProjectKey(settings.proxy.intercept.project_key)
|
@@ -134,10 +134,10 @@ if is_local:
|
|
134
134
|
Configure which tests to print. Defaults to {test_output_level.PASSED}.
|
135
135
|
'''
|
136
136
|
)
|
137
|
-
@click.option('--public-directory-path', help='Path to public files. Used for mocking requests.')
|
137
|
+
@click.option('--public-directory-path', multiple=True, help='Path to public files. Used for mocking requests. Can take the form <FOLDER-PATH>[:<ORIGIN>].')
|
138
138
|
@ConditionalDecorator(lambda f: click.option('--remote-project-key', help='Use remote project for endpoint definitions.')(f), is_remote and is_local)
|
139
139
|
@ConditionalDecorator(lambda f: click.option('--report-key', help='Save to report.')(f), is_remote)
|
140
|
-
@click.option('--response-fixtures-path', help='Path to response fixtures yaml. Used for mocking requests.')
|
140
|
+
@click.option('--response-fixtures-path', multiple=True, help='Path to response fixtures yaml. Used for mocking requests. Can take the form <FILE-PATH>[:<ORIGIN>].')
|
141
141
|
@ConditionalDecorator(lambda f: click.option('--save', is_flag=True, default=False, help='Saves test results.')(f), is_remote)
|
142
142
|
@click.option('--scheme', type=click.Choice(['http', 'https']), help='Rewrite request scheme.')
|
143
143
|
@click.option('--strategy', default=test_strategy.DIFF, type=click.Choice([test_strategy.CONTRACT, test_strategy.CUSTOM, test_strategy.DIFF, test_strategy.FUZZY]), help='How to test responses.')
|
@@ -1,5 +1,6 @@
|
|
1
1
|
import hashlib
|
2
2
|
import os
|
3
|
+
import re
|
3
4
|
import shutil
|
4
5
|
|
5
6
|
from stoobly_agent.config.data_dir import DataDir, DATA_DIR_NAME
|
@@ -62,10 +63,6 @@ class App():
|
|
62
63
|
def scaffold_namespace(self):
|
63
64
|
return self.__scaffold_namespace
|
64
65
|
|
65
|
-
@property
|
66
|
-
def scaffold_namespace_path(self):
|
67
|
-
return os.path.join(self.data_dir_path, self.scaffold_namespace)
|
68
|
-
|
69
66
|
@property
|
70
67
|
def dir_path(self):
|
71
68
|
return self.__dir_path
|
@@ -101,7 +98,7 @@ class App():
|
|
101
98
|
|
102
99
|
return services
|
103
100
|
|
104
|
-
def copy_folders_and_hidden_files(self, src, dst):
|
101
|
+
def copy_folders_and_hidden_files(self, src: str, dst: str, ignore: list = []):
|
105
102
|
os.makedirs(dst, exist_ok=True)
|
106
103
|
|
107
104
|
# Walk through the source directory
|
@@ -109,6 +106,18 @@ class App():
|
|
109
106
|
# Copy hidden files only
|
110
107
|
for file_name in files:
|
111
108
|
src_file_path = os.path.join(root, file_name)
|
109
|
+
|
110
|
+
ignored = False
|
111
|
+
|
112
|
+
# Skip files that match the ignore list pattern, use regex
|
113
|
+
for ignore_pattern in ignore:
|
114
|
+
if re.match(os.path.join(src, ignore_pattern), src_file_path):
|
115
|
+
ignored = True
|
116
|
+
break
|
117
|
+
|
118
|
+
if ignored:
|
119
|
+
continue
|
120
|
+
|
112
121
|
dst_file_path = os.path.join(dst, os.path.relpath(root, src), file_name)
|
113
122
|
|
114
123
|
if not file_name.startswith('.'):
|
@@ -44,10 +44,6 @@ class AppCommand(Command):
|
|
44
44
|
DOCKER_COMPOSE_NETWORKS
|
45
45
|
)
|
46
46
|
|
47
|
-
@property
|
48
|
-
def scaffold_namespace_path(self):
|
49
|
-
return self.app.scaffold_namespace_path
|
50
|
-
|
51
47
|
@property
|
52
48
|
def app_templates_root_dir(self):
|
53
49
|
return os.path.join(self.templates_root_dir, 'app')
|
@@ -1,7 +1,9 @@
|
|
1
1
|
import os
|
2
2
|
|
3
3
|
from .config import Config
|
4
|
-
from .constants import
|
4
|
+
from .constants import (
|
5
|
+
APP_DOCKER_SOCKET_PATH_ENV, APP_NAME_ENV, APP_PLUGINS_DELMITTER, APP_PLUGINS_ENV, APP_PROXY_PORT_ENV, APP_RUN_ON_ENV, APP_RUN_ON_DELIMITER, APP_UI_PORT_ENV, APP_VERSION_ENV, RUN_ON_DOCKER, RUN_ON_LOCAL
|
6
|
+
)
|
5
7
|
|
6
8
|
class AppConfig(Config):
|
7
9
|
|
@@ -11,6 +13,8 @@ class AppConfig(Config):
|
|
11
13
|
self.__docker_socket_path = '/var/run/docker.sock'
|
12
14
|
self.__name = None
|
13
15
|
self.__plugins = None
|
16
|
+
self.__proxy_port = None
|
17
|
+
self.__run_on = None
|
14
18
|
self.__ui_port = None
|
15
19
|
|
16
20
|
self.load()
|
@@ -39,24 +43,58 @@ class AppConfig(Config):
|
|
39
43
|
def plugins(self, v: list):
|
40
44
|
self.__plugins = v
|
41
45
|
|
46
|
+
@property
|
47
|
+
def run_on(self):
|
48
|
+
return self.__run_on or [RUN_ON_DOCKER]
|
49
|
+
|
50
|
+
@run_on.setter
|
51
|
+
def run_on(self, v: list):
|
52
|
+
self.__run_on = v
|
53
|
+
|
54
|
+
@property
|
55
|
+
def run_on_local(self):
|
56
|
+
return RUN_ON_LOCAL in self.run_on
|
57
|
+
|
58
|
+
@property
|
59
|
+
def proxy_port(self):
|
60
|
+
return self.__proxy_port or 8080
|
61
|
+
|
62
|
+
@proxy_port.setter
|
63
|
+
def proxy_port(self, v):
|
64
|
+
self.__proxy_port = v
|
65
|
+
|
42
66
|
@property
|
43
67
|
def ui_port(self):
|
44
|
-
return self.__ui_port
|
68
|
+
return self.__ui_port or 4200
|
45
69
|
|
46
70
|
@ui_port.setter
|
47
71
|
def ui_port(self, v):
|
48
72
|
self.__ui_port = v
|
49
73
|
|
74
|
+
@property
|
75
|
+
def version(self):
|
76
|
+
return self.__version or None
|
77
|
+
|
78
|
+
@version.setter
|
79
|
+
def version(self, v):
|
80
|
+
self.__version = v
|
81
|
+
|
50
82
|
def load(self, config = None):
|
51
83
|
config = config or self.read()
|
52
84
|
|
53
85
|
self.name = config.get(APP_NAME_ENV)
|
86
|
+
self.proxy_port = config.get(APP_PROXY_PORT_ENV)
|
54
87
|
self.ui_port = config.get(APP_UI_PORT_ENV)
|
88
|
+
self.version = config.get(APP_VERSION_ENV)
|
55
89
|
|
56
90
|
if config.get(APP_PLUGINS_ENV):
|
57
91
|
plugins: str = config.get(APP_PLUGINS_ENV)
|
58
92
|
self.plugins = plugins.split(APP_PLUGINS_DELMITTER)
|
59
93
|
|
94
|
+
if config.get(APP_RUN_ON_ENV):
|
95
|
+
run_on: str = config.get(APP_RUN_ON_ENV)
|
96
|
+
self.run_on = run_on.split(APP_RUN_ON_DELIMITER)
|
97
|
+
|
60
98
|
def write(self):
|
61
99
|
config = {}
|
62
100
|
|
@@ -69,7 +107,16 @@ class AppConfig(Config):
|
|
69
107
|
if self.plugins:
|
70
108
|
config[APP_PLUGINS_ENV] = APP_PLUGINS_DELMITTER.join(self.plugins)
|
71
109
|
|
110
|
+
if self.run_on:
|
111
|
+
config[APP_RUN_ON_ENV] = APP_RUN_ON_DELIMITER.join(self.run_on)
|
112
|
+
|
113
|
+
if self.proxy_port:
|
114
|
+
config[APP_PROXY_PORT_ENV] = self.proxy_port
|
115
|
+
|
72
116
|
if self.ui_port:
|
73
117
|
config[APP_UI_PORT_ENV] = self.ui_port
|
118
|
+
|
119
|
+
if self.version:
|
120
|
+
config[APP_VERSION_ENV] = self.version
|
74
121
|
|
75
122
|
super().write(config)
|
@@ -1,20 +1,25 @@
|
|
1
|
+
from math import e
|
1
2
|
import os
|
2
3
|
import pdb
|
3
|
-
import shutil
|
4
|
-
import yaml
|
5
4
|
|
6
|
-
|
5
|
+
import shutil
|
7
6
|
from typing import TypedDict
|
8
7
|
|
8
|
+
from stoobly_agent import VERSION
|
9
|
+
from stoobly_agent.app.cli.scaffold.docker.constants import DOCKER_COMPOSE_WORKFLOW
|
10
|
+
|
9
11
|
from .app import App
|
10
12
|
from .app_command import AppCommand
|
11
|
-
from .constants import PLUGIN_CYPRESS, PLUGIN_PLAYWRIGHT,
|
12
|
-
from .docker.
|
13
|
-
from .templates.constants import
|
13
|
+
from .constants import PLUGIN_CYPRESS, PLUGIN_PLAYWRIGHT, RUN_ON_DOCKER, RUN_ON_LOCAL
|
14
|
+
from .docker.template_files import plugin_docker_cypress, plugin_docker_playwright, plugin_local_cypress, plugin_local_playwright, remove_app_docker_files, remove_service_docker_files
|
15
|
+
from .templates.constants import CORE_GATEWAY_SERVICE_NAME, CORE_MOCK_UI_SERVICE_NAME, MAINTAINED_RUN
|
14
16
|
|
15
17
|
class AppCreateOptions(TypedDict):
|
16
18
|
docker_socket_path: str
|
17
19
|
name: str
|
20
|
+
plugin: list
|
21
|
+
proxy_port: int
|
22
|
+
run_on: list
|
18
23
|
ui_port: int
|
19
24
|
|
20
25
|
class AppCreateCommand(AppCommand):
|
@@ -31,6 +36,12 @@ class AppCreateCommand(AppCommand):
|
|
31
36
|
if kwargs.get('plugin'):
|
32
37
|
self.app_config.plugins = kwargs['plugin']
|
33
38
|
|
39
|
+
if kwargs.get('proxy_port'):
|
40
|
+
self.app_config.proxy_port = kwargs['proxy_port']
|
41
|
+
|
42
|
+
if kwargs.get('run_on'):
|
43
|
+
self.app_config.run_on = kwargs['run_on']
|
44
|
+
|
34
45
|
if kwargs.get('ui_port'):
|
35
46
|
self.app_config.ui_port = kwargs['ui_port']
|
36
47
|
|
@@ -46,40 +57,154 @@ class AppCreateCommand(AppCommand):
|
|
46
57
|
def app_plugins(self):
|
47
58
|
return self.app_config.plugins
|
48
59
|
|
60
|
+
@property
|
61
|
+
def app_run_on(self):
|
62
|
+
return self.app_config.run_on
|
63
|
+
|
64
|
+
@property
|
65
|
+
def app_proxy_port(self):
|
66
|
+
return self.app_config.proxy_port
|
67
|
+
|
68
|
+
@property
|
49
69
|
def app_ui_port(self):
|
50
70
|
return self.app_config.ui_port
|
51
71
|
|
72
|
+
@property
|
73
|
+
def app_version(self):
|
74
|
+
return self.app_config.version
|
75
|
+
|
52
76
|
def build(self):
|
77
|
+
self.__migrate()
|
78
|
+
|
53
79
|
dest = self.scaffold_namespace_path
|
80
|
+
ignore = []
|
81
|
+
warnings = []
|
54
82
|
|
55
|
-
self.
|
83
|
+
if RUN_ON_LOCAL in self.app_run_on:
|
84
|
+
ignore.append(f"{CORE_GATEWAY_SERVICE_NAME}/.*")
|
85
|
+
ignore.append(f"{CORE_MOCK_UI_SERVICE_NAME}/.*")
|
56
86
|
|
57
|
-
|
58
|
-
|
59
|
-
[os.path.join(CORE_GATEWAY_SERVICE_NAME, '.docker-compose.base.yml'), '**/.env']
|
60
|
-
))
|
87
|
+
if RUN_ON_DOCKER in self.app_run_on:
|
88
|
+
ignore.append(f".*/{MAINTAINED_RUN}")
|
61
89
|
|
62
|
-
|
90
|
+
# Copy all app templates
|
91
|
+
self.app.copy_folders_and_hidden_files(self.app_templates_root_dir, dest, ignore)
|
63
92
|
|
64
|
-
#
|
65
|
-
|
66
|
-
|
67
|
-
|
93
|
+
# Remove Docker-specific files if not using Docker
|
94
|
+
if RUN_ON_DOCKER not in self.app_run_on:
|
95
|
+
remove_app_docker_files(dest)
|
96
|
+
remove_service_docker_files(dest)
|
68
97
|
|
98
|
+
if PLUGIN_CYPRESS in self.app_plugins:
|
69
99
|
if not self.__cypress_initialized(self.app):
|
70
100
|
warnings.append(f"missing cypress.config.(js|ts), please run `npx cypress open` in {self.app.context_dir_path}")
|
71
101
|
|
72
|
-
|
73
102
|
if PLUGIN_PLAYWRIGHT in self.app_plugins:
|
74
|
-
self.__plugin_playwright(dest, PLUGIN_PLAYWRIGHT)
|
75
|
-
|
76
103
|
if not self.__playwright_initialized(self.app):
|
77
104
|
warnings.append(f"missing playwright.config.(js|ts), please run `npm init playwright@latest` in {self.app.context_dir_path}")
|
78
105
|
|
106
|
+
if RUN_ON_DOCKER in self.app_run_on:
|
107
|
+
with open(os.path.join(dest, '.gitignore'), 'w') as fp:
|
108
|
+
fp.write("\n".join(
|
109
|
+
[os.path.join(CORE_GATEWAY_SERVICE_NAME, '.docker-compose.base.yml'), '**/.env']
|
110
|
+
))
|
111
|
+
|
112
|
+
# Provide plugins
|
113
|
+
if PLUGIN_CYPRESS in self.app_plugins:
|
114
|
+
plugin_docker_cypress(self.templates_root_dir, PLUGIN_CYPRESS, dest)
|
115
|
+
|
116
|
+
if PLUGIN_PLAYWRIGHT in self.app_plugins:
|
117
|
+
plugin_docker_playwright(self.templates_root_dir, PLUGIN_PLAYWRIGHT, dest)
|
118
|
+
else:
|
119
|
+
if PLUGIN_CYPRESS in self.app_plugins:
|
120
|
+
plugin_local_cypress(self.templates_root_dir, PLUGIN_CYPRESS, dest)
|
121
|
+
|
122
|
+
if PLUGIN_PLAYWRIGHT in self.app_plugins:
|
123
|
+
plugin_local_playwright(self.templates_root_dir, PLUGIN_PLAYWRIGHT, dest)
|
124
|
+
|
125
|
+
with open(os.path.join(dest, '.gitignore'), 'w') as fp:
|
126
|
+
fp.write("\n".join(
|
127
|
+
['**/.env']
|
128
|
+
))
|
129
|
+
|
130
|
+
self.app_config.write()
|
131
|
+
|
79
132
|
return {
|
80
133
|
'warnings': warnings
|
81
134
|
}
|
82
135
|
|
136
|
+
def __compare_versions(self, version1, version2):
|
137
|
+
"""
|
138
|
+
Compare two semantic versions.
|
139
|
+
|
140
|
+
Args:
|
141
|
+
version1: First version string (e.g., '1.10.1')
|
142
|
+
version2: Second version string (e.g., '1.9.0')
|
143
|
+
|
144
|
+
Returns:
|
145
|
+
-1 if version1 < version2
|
146
|
+
0 if version1 == version2
|
147
|
+
1 if version1 > version2
|
148
|
+
"""
|
149
|
+
try:
|
150
|
+
v1_parts = [int(x) for x in version1.split('.')]
|
151
|
+
v2_parts = [int(x) for x in version2.split('.')]
|
152
|
+
|
153
|
+
# Pad shorter version with zeros
|
154
|
+
max_len = max(len(v1_parts), len(v2_parts))
|
155
|
+
v1_parts.extend([0] * (max_len - len(v1_parts)))
|
156
|
+
v2_parts.extend([0] * (max_len - len(v2_parts)))
|
157
|
+
|
158
|
+
for v1_part, v2_part in zip(v1_parts, v2_parts):
|
159
|
+
if v1_part < v2_part:
|
160
|
+
return -1
|
161
|
+
elif v1_part > v2_part:
|
162
|
+
return 1
|
163
|
+
|
164
|
+
return 0
|
165
|
+
except (ValueError, AttributeError):
|
166
|
+
# If version parsing fails, treat as invalid and return -1 (version1 < version2)
|
167
|
+
return -1
|
168
|
+
|
169
|
+
def __migrate(self):
|
170
|
+
if not self.app_version or self.__compare_versions(self.app_version, '1.10.0') < 0:
|
171
|
+
new_scaffold_namespace_path = self.scaffold_namespace_path
|
172
|
+
if not os.path.exists(new_scaffold_namespace_path):
|
173
|
+
old_scaffold_namespace_path = os.path.join(self.data_dir_path, 'docker')
|
174
|
+
if os.path.exists(old_scaffold_namespace_path):
|
175
|
+
shutil.move(old_scaffold_namespace_path, new_scaffold_namespace_path)
|
176
|
+
else:
|
177
|
+
os.makedirs(new_scaffold_namespace_path)
|
178
|
+
|
179
|
+
# For each file in self.scaffold_namespace_path/<SERVICE-NAME>/<WORKFLOW-NAME>/bin
|
180
|
+
# move it to self.scaffold_namespace_path/<SERVICE-NAME>/<WORKFLOW-NAME>
|
181
|
+
for service_name in os.listdir(new_scaffold_namespace_path):
|
182
|
+
service_path = os.path.join(new_scaffold_namespace_path, service_name)
|
183
|
+
if not os.path.isdir(service_path):
|
184
|
+
continue
|
185
|
+
|
186
|
+
for workflow_name in os.listdir(service_path):
|
187
|
+
workflow_path = os.path.join(new_scaffold_namespace_path, service_name, workflow_name)
|
188
|
+
if not os.path.isdir(workflow_path):
|
189
|
+
continue
|
190
|
+
|
191
|
+
docker_compose_workflow_path = os.path.join(workflow_path, f".docker-compose.{workflow_name}.yml")
|
192
|
+
if os.path.exists(docker_compose_workflow_path):
|
193
|
+
os.rename(docker_compose_workflow_path, os.path.join(workflow_path, DOCKER_COMPOSE_WORKFLOW))
|
194
|
+
|
195
|
+
bin_path = os.path.join(workflow_path, 'bin')
|
196
|
+
if not os.path.isdir(bin_path):
|
197
|
+
continue
|
198
|
+
|
199
|
+
for file in os.listdir(bin_path):
|
200
|
+
shutil.move(os.path.join(bin_path, file), os.path.join(workflow_path, file))
|
201
|
+
|
202
|
+
# Remove the bin folder
|
203
|
+
shutil.rmtree(bin_path)
|
204
|
+
|
205
|
+
self.app_config.version = VERSION
|
206
|
+
self.app_config.write()
|
207
|
+
|
83
208
|
def __cypress_initialized(self, app: App):
|
84
209
|
if os.path.exists(os.path.join(app.context_dir_path, 'cypress.config.js')):
|
85
210
|
return True
|
@@ -89,25 +214,6 @@ class AppCreateCommand(AppCommand):
|
|
89
214
|
|
90
215
|
return False
|
91
216
|
|
92
|
-
def __merge_compose_plugin(self, dest_path: str, template_path: str, plugin: str):
|
93
|
-
if not os.path.exists(dest_path):
|
94
|
-
open(dest_path, 'a').close()
|
95
|
-
|
96
|
-
def load_yaml(path):
|
97
|
-
with open(path, 'r') as f:
|
98
|
-
return yaml.safe_load(f) or {}
|
99
|
-
|
100
|
-
data1 = load_yaml(dest_path)
|
101
|
-
data2 = load_yaml(template_path)
|
102
|
-
|
103
|
-
services = data1.get('services') or {}
|
104
|
-
if services.get(PLUGIN_CONTAINER_SERVICE_TEMPLATE.format(plugin=plugin, service=CORE_ENTRYPOINT_SERVICE_NAME)):
|
105
|
-
return
|
106
|
-
|
107
|
-
with open(dest_path, 'w') as out:
|
108
|
-
merged = merge(data1, data2)
|
109
|
-
yaml.dump(merged, out, default_flow_style=False)
|
110
|
-
|
111
217
|
def __playwright_initialized(self, app: App):
|
112
218
|
if os.path.exists(os.path.join(app.context_dir_path, 'playwright.config.js')):
|
113
219
|
return True
|
@@ -115,41 +221,4 @@ class AppCreateCommand(AppCommand):
|
|
115
221
|
if os.path.exists(os.path.join(app.context_dir_path, 'playwright.config.ts')):
|
116
222
|
return True
|
117
223
|
|
118
|
-
return False
|
119
|
-
|
120
|
-
def __plugin_cypress(self, dest: str, plugin: str):
|
121
|
-
dockerfile_name = PLUGIN_DOCKERFILE_TEMPLATE.format(plugin=plugin)
|
122
|
-
dockerfile_dest_path = os.path.join(dest, CORE_ENTRYPOINT_SERVICE_NAME, WORKFLOW_TEST_TYPE, dockerfile_name)
|
123
|
-
|
124
|
-
# Copy Dockerfile to workflow
|
125
|
-
dockerfile_src_path = os.path.join(self.templates_root_dir, PLUGINS_FOLDER, plugin, WORKFLOW_TEST_TYPE, dockerfile_name)
|
126
|
-
shutil.copyfile(dockerfile_src_path, dockerfile_dest_path)
|
127
|
-
|
128
|
-
# Merge template into dest compose yml
|
129
|
-
compose_dest_path = os.path.join(
|
130
|
-
dest, CORE_ENTRYPOINT_SERVICE_NAME, WORKFLOW_TEST_TYPE, DOCKER_COMPOSE_WORKFLOW_TEMPLATE.format(workflow=WORKFLOW_TEST_TYPE)
|
131
|
-
)
|
132
|
-
template_path = os.path.join(
|
133
|
-
self.templates_root_dir, PLUGINS_FOLDER, plugin, WORKFLOW_TEST_TYPE, DOCKER_COMPOSE_WORKFLOW_TEMPLATE.format(workflow=WORKFLOW_TEST_TYPE)
|
134
|
-
)
|
135
|
-
self.__merge_compose_plugin(compose_dest_path, template_path, plugin)
|
136
|
-
|
137
|
-
def __plugin_playwright(self, dest: str, plugin: str):
|
138
|
-
# Copy Dockerfile to workflow
|
139
|
-
dockerfile_name = PLUGIN_DOCKERFILE_TEMPLATE.format(plugin=plugin)
|
140
|
-
dockerfile_src_path = os.path.join(self.templates_root_dir, PLUGINS_FOLDER, plugin, WORKFLOW_TEST_TYPE, dockerfile_name)
|
141
|
-
dockerfile_dest_path = os.path.join(dest, CORE_ENTRYPOINT_SERVICE_NAME, WORKFLOW_TEST_TYPE, dockerfile_name)
|
142
|
-
shutil.copyfile(dockerfile_src_path, dockerfile_dest_path)
|
143
|
-
|
144
|
-
entrypoint_src_path = os.path.join(self.templates_root_dir, PLUGINS_FOLDER, plugin, WORKFLOW_TEST_TYPE, PLUGIN_DOCKER_ENTRYPOINT)
|
145
|
-
entrypoint_dest_path = os.path.join(dest, CORE_ENTRYPOINT_SERVICE_NAME, WORKFLOW_TEST_TYPE, PLUGIN_DOCKER_ENTRYPOINT)
|
146
|
-
shutil.copyfile(entrypoint_src_path, entrypoint_dest_path)
|
147
|
-
|
148
|
-
# Merge template into dest compose yml
|
149
|
-
compose_dest_path = os.path.join(
|
150
|
-
dest, CORE_ENTRYPOINT_SERVICE_NAME, WORKFLOW_TEST_TYPE, DOCKER_COMPOSE_WORKFLOW_TEMPLATE.format(workflow=WORKFLOW_TEST_TYPE)
|
151
|
-
)
|
152
|
-
template_path = os.path.join(
|
153
|
-
self.templates_root_dir, PLUGINS_FOLDER, plugin, WORKFLOW_TEST_TYPE, DOCKER_COMPOSE_WORKFLOW_TEMPLATE.format(workflow=WORKFLOW_TEST_TYPE)
|
154
|
-
)
|
155
|
-
self.__merge_compose_plugin(compose_dest_path, template_path, plugin)
|
224
|
+
return False
|
@@ -11,20 +11,26 @@ APP_NETWORK_ENV = 'APP_NETWORK'
|
|
11
11
|
APP_NAME_ENV = 'APP_NAME'
|
12
12
|
APP_PLUGINS_ENV = 'APP_PLUGINS'
|
13
13
|
APP_PLUGINS_DELMITTER = ','
|
14
|
+
APP_PROXY_PORT_ENV = 'APP_PROXY_PORT'
|
15
|
+
APP_RUN_ON_ENV = 'APP_RUN_ON'
|
16
|
+
APP_RUN_ON_DELIMITER = ','
|
14
17
|
APP_UI_PORT_ENV = 'APP_UI_PORT'
|
18
|
+
APP_VERSION_ENV = 'APP_VERSION'
|
15
19
|
BIN_FOLDER_NAME = 'bin'
|
16
20
|
CA_CERTS_DIR_ENV = 'CA_CERTS_DIR'
|
17
21
|
CERTS_DIR_ENV = 'CERTS_DIR'
|
18
|
-
COMPOSE_TEMPLATE = '.docker-compose.{workflow}.yml'
|
19
22
|
CONFIG_FILE = '.config.yml'
|
20
23
|
CONTEXT_DIR_ENV = 'CONTEXT_DIR'
|
21
24
|
DOTENV_FILE = '.env'
|
22
25
|
DOTENV_PATH_ENV = 'STOOBLY_DOTENV_PATH'
|
26
|
+
FIXTURES_FILE_NAME = 'fixtures.yml'
|
23
27
|
NAMESERVERS_FILE = '.nameservers'
|
24
28
|
PLUGIN_CYPRESS = 'cypress'
|
25
29
|
PLUGIN_PLAYWRIGHT = 'playwright'
|
26
30
|
PLUGINS_FOLDER = 'plugins'
|
27
31
|
PUBLIC_FOLDER_NAME = 'public'
|
32
|
+
RUN_ON_DOCKER = 'docker'
|
33
|
+
RUN_ON_LOCAL = 'local'
|
28
34
|
SERVICE_DETACHED = '${SERVICE_DETACHED}'
|
29
35
|
SERVICE_DETACHED_ENV = 'SERVICE_DETACHED'
|
30
36
|
SERVICE_DNS = '${SERVICE_DNS}'
|
@@ -78,3 +84,4 @@ WORKFLOW_TEST_TYPE = 'test'
|
|
78
84
|
|
79
85
|
CORE_WORKFLOWS = [WORKFLOW_MOCK_TYPE, WORKFLOW_RECORD_TYPE, WORKFLOW_TEST_TYPE]
|
80
86
|
WORKFLOW_TEMPLATE_OPTION = Literal[WORKFLOW_MOCK_TYPE, WORKFLOW_RECORD_TYPE, WORKFLOW_TEST_TYPE]
|
87
|
+
RUN_ON_OPTIONS = [RUN_ON_DOCKER, RUN_ON_LOCAL]
|
@@ -8,7 +8,9 @@ DOCKER_COMPOSE_BASE = '.docker-compose.base.yml'
|
|
8
8
|
DOCKER_COMPOSE_BASE_TEMPLATE = '.docker-compose.base.template.yml'
|
9
9
|
DOCKER_COMPOSE_CUSTOM = 'docker-compose.yml'
|
10
10
|
DOCKER_COMPOSE_NETWORKS = '.docker-compose.networks.yml'
|
11
|
-
|
11
|
+
DOCKER_COMPOSE_WORKFLOW = '.docker-compose.yml'
|
12
|
+
DOCKER_MAKEFILE = 'Makefile'
|
13
|
+
DOCKER_MAKEFILE_DOT = '.Makefile'
|
12
14
|
DOCKERFILE_CONTEXT = '.Dockerfile.context'
|
13
15
|
DOCKERFILE_SERVICE = 'Dockerfile.source'
|
14
16
|
PLUGIN_CONTAINER_SERVICE_TEMPLATE = '{service}.{plugin}'
|
@@ -4,12 +4,12 @@ from typing import TypedDict
|
|
4
4
|
|
5
5
|
from ...constants import SERVICES_NAMESPACE
|
6
6
|
from ..constants import DOCKERFILE_SERVICE
|
7
|
-
from .builder import
|
7
|
+
from .builder import DockerServiceBuilder
|
8
8
|
from .types import BuildDecoratorOptions
|
9
9
|
|
10
10
|
class BuildDecorator():
|
11
11
|
|
12
|
-
def __init__(self, service_builder:
|
12
|
+
def __init__(self, service_builder: DockerServiceBuilder):
|
13
13
|
self.__service_builder = service_builder
|
14
14
|
|
15
15
|
def decorate(self, **kwargs: BuildDecoratorOptions):
|