stoobly-agent 1.9.12__py3-none-any.whl → 1.10.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- stoobly_agent/__init__.py +1 -1
- stoobly_agent/app/api/__init__.py +4 -20
- stoobly_agent/app/api/application_http_request_handler.py +5 -2
- stoobly_agent/app/api/configs_controller.py +3 -3
- stoobly_agent/app/cli/decorators/exec.py +1 -1
- stoobly_agent/app/cli/helpers/handle_config_update_service.py +4 -0
- stoobly_agent/app/cli/intercept_cli.py +40 -7
- stoobly_agent/app/cli/scaffold/app_command.py +4 -0
- stoobly_agent/app/cli/scaffold/app_config.py +21 -3
- stoobly_agent/app/cli/scaffold/app_create_command.py +109 -2
- stoobly_agent/app/cli/scaffold/constants.py +14 -3
- stoobly_agent/app/cli/scaffold/docker/constants.py +4 -6
- stoobly_agent/app/cli/scaffold/docker/service/build_decorator.py +2 -2
- stoobly_agent/app/cli/scaffold/docker/service/builder.py +36 -10
- stoobly_agent/app/cli/scaffold/docker/workflow/builder.py +0 -27
- stoobly_agent/app/cli/scaffold/docker/workflow/command_decorator.py +25 -0
- stoobly_agent/app/cli/scaffold/docker/workflow/decorators_factory.py +7 -2
- stoobly_agent/app/cli/scaffold/docker/workflow/detached_decorator.py +42 -0
- stoobly_agent/app/cli/scaffold/docker/workflow/local_decorator.py +26 -0
- stoobly_agent/app/cli/scaffold/docker/workflow/mock_decorator.py +9 -10
- stoobly_agent/app/cli/scaffold/docker/workflow/reverse_proxy_decorator.py +5 -8
- stoobly_agent/app/cli/scaffold/service_config.py +133 -34
- stoobly_agent/app/cli/scaffold/service_create_command.py +11 -2
- stoobly_agent/app/cli/scaffold/service_dependency.py +51 -0
- stoobly_agent/app/cli/scaffold/service_docker_compose.py +3 -3
- stoobly_agent/app/cli/scaffold/service_workflow_validate_command.py +10 -7
- stoobly_agent/app/cli/scaffold/templates/app/.Dockerfile.context +1 -1
- stoobly_agent/app/cli/scaffold/templates/app/build/.docker-compose.base.yml +2 -2
- stoobly_agent/app/cli/scaffold/templates/app/build/mock/bin/configure +1 -1
- stoobly_agent/app/cli/scaffold/templates/app/build/mock/docker-compose.yml +16 -6
- stoobly_agent/app/cli/scaffold/templates/app/build/record/bin/configure +26 -1
- stoobly_agent/app/cli/scaffold/templates/app/build/record/docker-compose.yml +16 -6
- stoobly_agent/app/cli/scaffold/templates/app/build/test/bin/configure +1 -1
- stoobly_agent/app/cli/scaffold/templates/app/build/test/docker-compose.yml +16 -6
- stoobly_agent/app/cli/scaffold/templates/app/entrypoint/.docker-compose.base.yml +2 -2
- stoobly_agent/app/cli/scaffold/templates/app/entrypoint/mock/bin/configure +1 -1
- stoobly_agent/app/cli/scaffold/templates/app/entrypoint/mock/docker-compose.yml +16 -10
- stoobly_agent/app/cli/scaffold/templates/app/entrypoint/record/bin/configure +1 -1
- stoobly_agent/app/cli/scaffold/templates/app/entrypoint/record/docker-compose.yml +16 -10
- stoobly_agent/app/cli/scaffold/templates/app/entrypoint/test/bin/configure +1 -1
- stoobly_agent/app/cli/scaffold/templates/app/entrypoint/test/docker-compose.yml +16 -10
- stoobly_agent/app/cli/scaffold/templates/app/stoobly-ui/.docker-compose.base.yml +2 -1
- stoobly_agent/app/cli/scaffold/templates/app/stoobly-ui/mock/.docker-compose.mock.yml +6 -3
- stoobly_agent/app/cli/scaffold/templates/app/stoobly-ui/record/.docker-compose.record.yml +6 -4
- stoobly_agent/app/cli/scaffold/templates/build/workflows/record/.configure +21 -1
- stoobly_agent/app/cli/scaffold/templates/constants.py +4 -0
- stoobly_agent/app/cli/scaffold/templates/plugins/cypress/test/.Dockerfile.cypress +22 -0
- stoobly_agent/app/cli/scaffold/templates/plugins/cypress/test/.docker-compose.test.yml +19 -0
- stoobly_agent/app/cli/scaffold/templates/plugins/playwright/test/.Dockerfile.playwright +33 -0
- stoobly_agent/app/cli/scaffold/templates/plugins/playwright/test/.docker-compose.test.yml +18 -0
- stoobly_agent/app/cli/scaffold/templates/plugins/playwright/test/.entrypoint.sh +11 -0
- stoobly_agent/app/cli/scaffold/templates/workflow/mock/bin/configure +2 -10
- stoobly_agent/app/cli/scaffold/templates/workflow/mock/docker-compose.yml +17 -0
- stoobly_agent/app/cli/scaffold/templates/workflow/record/bin/configure +19 -45
- stoobly_agent/app/cli/scaffold/templates/workflow/record/docker-compose.yml +17 -0
- stoobly_agent/app/cli/scaffold/templates/workflow/test/bin/configure +2 -10
- stoobly_agent/app/cli/scaffold/templates/workflow/test/docker-compose.yml +17 -0
- stoobly_agent/app/cli/scaffold/workflow_create_command.py +0 -1
- stoobly_agent/app/cli/scaffold/workflow_run_command.py +1 -1
- stoobly_agent/app/cli/scaffold_cli.py +85 -96
- stoobly_agent/app/proxy/handle_record_service.py +12 -3
- stoobly_agent/app/proxy/handle_replay_service.py +14 -2
- stoobly_agent/app/proxy/intercept_settings.py +12 -8
- stoobly_agent/app/proxy/record/upload_request_service.py +5 -8
- stoobly_agent/app/proxy/replay/replay_request_service.py +3 -0
- stoobly_agent/app/proxy/run.py +3 -28
- stoobly_agent/app/proxy/utils/allowed_request_service.py +3 -2
- stoobly_agent/app/proxy/utils/minimize_headers.py +47 -0
- stoobly_agent/app/proxy/utils/publish_change_service.py +22 -24
- stoobly_agent/app/proxy/utils/strategy.py +16 -0
- stoobly_agent/app/settings/__init__.py +15 -6
- stoobly_agent/app/settings/data_rules.py +25 -1
- stoobly_agent/app/settings/intercept_settings.py +5 -2
- stoobly_agent/app/settings/types/__init__.py +0 -1
- stoobly_agent/app/settings/ui_settings.py +5 -5
- stoobly_agent/cli.py +41 -16
- stoobly_agent/config/constants/custom_headers.py +1 -0
- stoobly_agent/config/constants/env_vars.py +4 -3
- stoobly_agent/config/constants/record_strategy.py +6 -0
- stoobly_agent/config/data_dir.py +1 -0
- stoobly_agent/config/settings.yml.sample +2 -3
- stoobly_agent/lib/logger.py +15 -5
- stoobly_agent/public/index.html +1 -1
- stoobly_agent/public/main-es2015.5a9aa16433404c3f423a.js +1 -0
- stoobly_agent/public/main-es5.5a9aa16433404c3f423a.js +1 -0
- stoobly_agent/test/app/cli/intercept/intercept_configure_test.py +231 -1
- stoobly_agent/test/app/cli/scaffold/cli_invoker.py +3 -2
- stoobly_agent/test/app/cli/scaffold/cli_test.py +3 -3
- stoobly_agent/test/app/cli/scaffold/e2e_test.py +11 -11
- stoobly_agent/test/app/models/schemas/.stoobly/db/VERSION +1 -1
- stoobly_agent/test/app/proxy/utils/minimize_headers_test.py +342 -0
- {stoobly_agent-1.9.12.dist-info → stoobly_agent-1.10.1.dist-info}/METADATA +2 -1
- {stoobly_agent-1.9.12.dist-info → stoobly_agent-1.10.1.dist-info}/RECORD +96 -80
- stoobly_agent/public/main-es2015.089b46f303768fbe864f.js +0 -1
- stoobly_agent/public/main-es5.089b46f303768fbe864f.js +0 -1
- {stoobly_agent-1.9.12.dist-info → stoobly_agent-1.10.1.dist-info}/LICENSE +0 -0
- {stoobly_agent-1.9.12.dist-info → stoobly_agent-1.10.1.dist-info}/WHEEL +0 -0
- {stoobly_agent-1.9.12.dist-info → stoobly_agent-1.10.1.dist-info}/entry_points.txt +0 -0
stoobly_agent/__init__.py
CHANGED
@@ -1,2 +1,2 @@
|
|
1
1
|
COMMAND = 'stoobly-agent'
|
2
|
-
VERSION = '1.
|
2
|
+
VERSION = '1.10.1'
|
@@ -5,7 +5,6 @@ import threading
|
|
5
5
|
from http.server import HTTPServer
|
6
6
|
from urllib.parse import urlparse
|
7
7
|
|
8
|
-
from stoobly_agent.app.settings import Settings
|
9
8
|
from stoobly_agent.config.constants import env_vars
|
10
9
|
from stoobly_agent.lib.logger import Logger
|
11
10
|
|
@@ -17,22 +16,7 @@ def start_server(host, port):
|
|
17
16
|
httpd = HTTPServer((host, port), ApplicationHTTPRequestHandler)
|
18
17
|
httpd.serve_forever()
|
19
18
|
|
20
|
-
def
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
# TODO: debug why the following is needed or else logger won't print
|
26
|
-
settings = Settings.instance()
|
27
|
-
settings.ui.url = url
|
28
|
-
settings.commit()
|
29
|
-
|
30
|
-
return url
|
31
|
-
|
32
|
-
def run(url):
|
33
|
-
parsed_url = urlparse(url)
|
34
|
-
|
35
|
-
Logger.instance(LOG_ID).info(f"Server listening at {url}\n")
|
36
|
-
|
37
|
-
thread = threading.Thread(target=start_server, args=(parsed_url.hostname, parsed_url.port))
|
38
|
-
thread.start()
|
19
|
+
def run(**kwargs):
|
20
|
+
Logger.instance(LOG_ID).info(f"starting and listening at {kwargs['ui_host']}:{kwargs['ui_port']}")
|
21
|
+
thread = threading.Thread(target=start_server, args=(kwargs['ui_host'], kwargs['ui_port']))
|
22
|
+
return thread.start()
|
@@ -104,11 +104,14 @@ class ApplicationHTTPRequestHandler(SimpleHTTPRequestHandler):
|
|
104
104
|
headers.CLIENT.title(),
|
105
105
|
custom_headers.DO_PROXY.title(),
|
106
106
|
headers.EXPIRY.title(),
|
107
|
+
custom_headers.REQUEST_ORIGIN.title(),
|
108
|
+
headers.TOKEN_TYPE.title(),
|
109
|
+
headers.UID.title(),
|
110
|
+
|
111
|
+
# ProxyController headers
|
107
112
|
headers.PROXY_HEADERS.title(),
|
108
113
|
headers.REQUEST_PATH.title(),
|
109
114
|
custom_headers.SERVICE_URL.title(),
|
110
|
-
headers.TOKEN_TYPE.title(),
|
111
|
-
headers.UID.title(),
|
112
115
|
])
|
113
116
|
self.send_header(header, allowed_headers)
|
114
117
|
rendered_headers.append(header)
|
@@ -4,7 +4,7 @@ from mergedeep import merge
|
|
4
4
|
|
5
5
|
from stoobly_agent.app.api.simple_http_request_handler import SimpleHTTPRequestHandler
|
6
6
|
from stoobly_agent.app.cli.helpers.handle_config_update_service import (
|
7
|
-
context as handle_context, handle_intercept_active_update, handle_order_update, handle_project_update, handle_scenario_update
|
7
|
+
context as handle_context, handle_intercept_active_update, handle_order_update, handle_project_update, handle_scenario_update, handle_strategy_update
|
8
8
|
)
|
9
9
|
from stoobly_agent.app.models.scenario_model import ScenarioModel
|
10
10
|
from stoobly_agent.app.proxy.intercept_settings import InterceptSettings
|
@@ -61,7 +61,7 @@ class ConfigsController:
|
|
61
61
|
|
62
62
|
# GET /configs/summary
|
63
63
|
def summary(self, context):
|
64
|
-
settings = Settings.instance()
|
64
|
+
settings: Settings = Settings.instance()
|
65
65
|
proxy = settings.proxy
|
66
66
|
intercept_settings = InterceptSettings(settings)
|
67
67
|
|
@@ -94,7 +94,6 @@ class ConfigsController:
|
|
94
94
|
'mode': intercept_settings.mode,
|
95
95
|
'modes': modes,
|
96
96
|
'project_id': int(project_id) if project_id != None else None,
|
97
|
-
'proxy_url': proxy.url,
|
98
97
|
'remote_enabled': settings.cli.features.remote,
|
99
98
|
'remote_project_id': remote_project_id,
|
100
99
|
'scenario_id': int(scenario_id) if scenario_id != None else None,
|
@@ -113,6 +112,7 @@ class ConfigsController:
|
|
113
112
|
|
114
113
|
handle_intercept_active_update(settings, _handle_context)
|
115
114
|
handle_order_update(settings, _handle_context)
|
115
|
+
handle_strategy_update(settings, _handle_context)
|
116
116
|
handle_project_update(settings, _handle_context)
|
117
117
|
handle_scenario_update(settings, _handle_context)
|
118
118
|
|
@@ -127,6 +127,10 @@ def handle_order_update(new_settings: Settings, context: Context = None):
|
|
127
127
|
scenario_model = ScenarioModel(new_settings)
|
128
128
|
scenario_model.update(_scenario_key.id, **{ 'overwritable': False })[1]
|
129
129
|
|
130
|
+
def handle_strategy_update(new_settings: Settings, context: Context = None):
|
131
|
+
# Handle side effects here
|
132
|
+
pass
|
133
|
+
|
130
134
|
def __current_proxy_settings(context: Context = None):
|
131
135
|
if context and context.get('current_proxy_settings'):
|
132
136
|
return context['current_proxy_settings']
|
@@ -3,27 +3,27 @@ import pdb
|
|
3
3
|
import sys
|
4
4
|
|
5
5
|
from stoobly_agent.app.settings import Settings
|
6
|
-
from stoobly_agent.config.constants import mode, mock_policy, record_order, record_policy, replay_policy
|
6
|
+
from stoobly_agent.config.constants import mode, mock_policy, record_order, record_policy, record_strategy, replay_policy, test_strategy
|
7
7
|
from stoobly_agent.lib.api.keys.project_key import ProjectKey
|
8
8
|
|
9
|
-
from .helpers.handle_config_update_service import handle_intercept_active_update, handle_order_update
|
9
|
+
from .helpers.handle_config_update_service import handle_intercept_active_update, handle_order_update, handle_strategy_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, mode.TEST]
|
14
14
|
|
15
15
|
if settings.cli.features.remote:
|
16
16
|
mode_options.append(mode.TEST)
|
17
17
|
|
18
18
|
active_mode = settings.proxy.intercept.mode
|
19
19
|
|
20
|
-
def __get_order_options(active_mode):
|
20
|
+
def __get_order_options(active_mode: str) -> list[str]:
|
21
21
|
if active_mode == mode.RECORD:
|
22
22
|
return [record_order.APPEND, record_order.OVERWRITE]
|
23
23
|
else:
|
24
24
|
return []
|
25
25
|
|
26
|
-
def __get_policy_options(active_mode):
|
26
|
+
def __get_policy_options(active_mode: str) -> list[str]:
|
27
27
|
if active_mode == mode.MOCK:
|
28
28
|
return [mock_policy.ALL, mock_policy.FOUND]
|
29
29
|
elif active_mode == mode.RECORD:
|
@@ -35,8 +35,17 @@ def __get_policy_options(active_mode):
|
|
35
35
|
else:
|
36
36
|
return []
|
37
37
|
|
38
|
+
def __get_strategy_options(active_mode: str) -> list[str]:
|
39
|
+
if active_mode == mode.RECORD:
|
40
|
+
return [record_strategy.FULL, record_strategy.MINIMAL]
|
41
|
+
elif active_mode == mode.TEST:
|
42
|
+
return [test_strategy.CONTRACT, test_strategy.CUSTOM, test_strategy.DIFF, test_strategy.FUZZY]
|
43
|
+
else:
|
44
|
+
return []
|
45
|
+
|
38
46
|
order_options = __get_order_options(active_mode)
|
39
47
|
policy_options = __get_policy_options(active_mode)
|
48
|
+
strategy_options = __get_strategy_options(active_mode)
|
40
49
|
|
41
50
|
@click.group(
|
42
51
|
epilog="Run 'stoobly-agent intercept COMMAND --help' for more information on a command.",
|
@@ -80,10 +89,11 @@ def disable(**kwargs):
|
|
80
89
|
@click.option('--mode', type=click.Choice(mode_options))
|
81
90
|
@click.option('--order', type=click.Choice(order_options))
|
82
91
|
@click.option('--policy', type=click.Choice(policy_options))
|
92
|
+
@click.option('--strategy', type=click.Choice(strategy_options))
|
83
93
|
def configure(**kwargs):
|
84
94
|
settings: Settings = Settings.instance()
|
85
95
|
|
86
|
-
if not kwargs['mode'] and not kwargs['order'] and not kwargs['policy']:
|
96
|
+
if not kwargs['mode'] and not kwargs['order'] and not kwargs['policy'] and not kwargs['strategy']:
|
87
97
|
print("Error: Missing an option")
|
88
98
|
sys.exit(1)
|
89
99
|
|
@@ -136,6 +146,26 @@ def configure(**kwargs):
|
|
136
146
|
|
137
147
|
print(f"Updating {_mode} policy to {kwargs['policy']}")
|
138
148
|
|
149
|
+
if kwargs['strategy']:
|
150
|
+
active_mode = settings.proxy.intercept.mode
|
151
|
+
|
152
|
+
if active_mode == mode.RECORD or active_mode == mode.TEST:
|
153
|
+
project_key = ProjectKey(settings.proxy.intercept.project_key)
|
154
|
+
data_rule = settings.proxy.data.data_rules(project_key.id)
|
155
|
+
|
156
|
+
if active_mode == mode.RECORD:
|
157
|
+
data_rule.record_strategy = kwargs['strategy']
|
158
|
+
elif active_mode == mode.TEST:
|
159
|
+
data_rule.test_strategy = kwargs['strategy']
|
160
|
+
else:
|
161
|
+
print("Error: set --strategy to a intercept mode that supports the strategy option", file=sys.stderr)
|
162
|
+
sys.exit(1)
|
163
|
+
|
164
|
+
handle_strategy_update(settings)
|
165
|
+
|
166
|
+
print(f"Updating {_mode} policy to {kwargs['strategy']}")
|
167
|
+
|
168
|
+
|
139
169
|
settings.commit()
|
140
170
|
|
141
171
|
@intercept.command(
|
@@ -148,17 +178,20 @@ def show(**kwargs):
|
|
148
178
|
project_key = ProjectKey(settings.proxy.intercept.project_key)
|
149
179
|
data_rule = settings.proxy.data.data_rules(project_key.id)
|
150
180
|
policy = None
|
181
|
+
strategy = None
|
151
182
|
|
152
183
|
if active_mode == mode.MOCK:
|
153
184
|
policy = data_rule.mock_policy
|
154
185
|
elif active_mode == mode.RECORD:
|
155
186
|
policy = data_rule.record_policy
|
187
|
+
strategy = data_rule.record_strategy
|
156
188
|
elif active_mode == mode.REPLAY:
|
157
189
|
policy = data_rule.replay_policy
|
158
190
|
elif active_mode == mode.TEST:
|
159
191
|
policy = data_rule.test_policy
|
192
|
+
strategy = data_rule.test_strategy
|
160
193
|
|
161
194
|
if not _mode:
|
162
195
|
print('No intercept mode set')
|
163
196
|
else:
|
164
|
-
print(f"{_mode.capitalize()} with policy {policy} {'enabled' if settings.proxy.intercept.active else 'disabled'}")
|
197
|
+
print(f"{_mode.capitalize()} with policy: '{policy}', strategy: '{strategy}', {'enabled' if settings.proxy.intercept.active else 'disabled'}")
|
@@ -56,6 +56,10 @@ class AppCommand(Command):
|
|
56
56
|
def data_dir_path(self):
|
57
57
|
return self.app.data_dir_path
|
58
58
|
|
59
|
+
@property
|
60
|
+
def plugins_root_dir(self):
|
61
|
+
return os.path.join(self.templates_root_dir, 'plugins')
|
62
|
+
|
59
63
|
@property
|
60
64
|
def scaffold_namespace_path(self):
|
61
65
|
return self.app.scaffold_namespace_path
|
@@ -1,5 +1,7 @@
|
|
1
|
+
import os
|
2
|
+
|
1
3
|
from .config import Config
|
2
|
-
from .constants import APP_DOCKER_SOCKET_PATH_ENV, APP_NAME_ENV, APP_UI_PORT_ENV
|
4
|
+
from .constants import APP_DOCKER_SOCKET_PATH_ENV, APP_NAME_ENV, APP_NETWORK_ENV, APP_PLUGINS_DELMITTER, APP_PLUGINS_ENV, APP_UI_PORT_ENV
|
3
5
|
|
4
6
|
class AppConfig(Config):
|
5
7
|
|
@@ -8,6 +10,7 @@ class AppConfig(Config):
|
|
8
10
|
|
9
11
|
self.__docker_socket_path = '/var/run/docker.sock'
|
10
12
|
self.__name = None
|
13
|
+
self.__plugins = None
|
11
14
|
self.__ui_port = None
|
12
15
|
|
13
16
|
self.load()
|
@@ -28,6 +31,14 @@ class AppConfig(Config):
|
|
28
31
|
def name(self, v):
|
29
32
|
self.__name = v
|
30
33
|
|
34
|
+
@property
|
35
|
+
def plugins(self):
|
36
|
+
return self.__plugins or []
|
37
|
+
|
38
|
+
@plugins.setter
|
39
|
+
def plugins(self, v: list):
|
40
|
+
self.__plugins = v
|
41
|
+
|
31
42
|
@property
|
32
43
|
def ui_port(self):
|
33
44
|
return self.__ui_port
|
@@ -41,7 +52,11 @@ class AppConfig(Config):
|
|
41
52
|
|
42
53
|
self.name = config.get(APP_NAME_ENV)
|
43
54
|
self.ui_port = config.get(APP_UI_PORT_ENV)
|
44
|
-
|
55
|
+
|
56
|
+
if config.get(APP_PLUGINS_ENV):
|
57
|
+
plugins: str = config.get(APP_PLUGINS_ENV)
|
58
|
+
self.plugins = plugins.split(APP_PLUGINS_DELMITTER)
|
59
|
+
|
45
60
|
def write(self):
|
46
61
|
config = {}
|
47
62
|
|
@@ -51,7 +66,10 @@ class AppConfig(Config):
|
|
51
66
|
if self.name:
|
52
67
|
config[APP_NAME_ENV] = self.name
|
53
68
|
|
69
|
+
if self.plugins:
|
70
|
+
config[APP_PLUGINS_ENV] = APP_PLUGINS_DELMITTER.join(self.plugins)
|
71
|
+
|
54
72
|
if self.ui_port:
|
55
73
|
config[APP_UI_PORT_ENV] = self.ui_port
|
56
74
|
|
57
|
-
super().write(config)
|
75
|
+
super().write(config)
|
@@ -1,10 +1,16 @@
|
|
1
1
|
import os
|
2
2
|
import pdb
|
3
|
+
import shutil
|
4
|
+
import yaml
|
3
5
|
|
6
|
+
from mergedeep import merge
|
4
7
|
from typing import TypedDict
|
5
8
|
|
6
9
|
from .app import App
|
7
10
|
from .app_command import AppCommand
|
11
|
+
from .constants import PLUGIN_CYPRESS, PLUGIN_PLAYWRIGHT, PLUGINS_FOLDER, WORKFLOW_TEST_TYPE
|
12
|
+
from .docker.constants import DOCKER_COMPOSE_WORKFLOW_TEMPLATE, PLUGIN_CONTAINER_SERVICE_TEMPLATE, PLUGIN_DOCKER_ENTRYPOINT, PLUGIN_DOCKERFILE_TEMPLATE
|
13
|
+
from .templates.constants import CORE_ENTRYPOINT_SERVICE_NAME, CORE_GATEWAY_SERVICE_NAME
|
8
14
|
|
9
15
|
class AppCreateOptions(TypedDict):
|
10
16
|
docker_socket_path: str
|
@@ -22,6 +28,9 @@ class AppCreateCommand(AppCommand):
|
|
22
28
|
if kwargs.get('docker_socket_path'):
|
23
29
|
self.app_config.docker_socket_path = kwargs['docker_socket_path']
|
24
30
|
|
31
|
+
if kwargs.get('plugin'):
|
32
|
+
self.app_config.plugins = kwargs['plugin']
|
33
|
+
|
25
34
|
if kwargs.get('ui_port'):
|
26
35
|
self.app_config.ui_port = kwargs['ui_port']
|
27
36
|
|
@@ -34,6 +43,9 @@ class AppCreateCommand(AppCommand):
|
|
34
43
|
return self.app_config.name
|
35
44
|
|
36
45
|
@property
|
46
|
+
def app_plugins(self):
|
47
|
+
return self.app_config.plugins
|
48
|
+
|
37
49
|
def app_ui_port(self):
|
38
50
|
return self.app_config.ui_port
|
39
51
|
|
@@ -43,6 +55,101 @@ class AppCreateCommand(AppCommand):
|
|
43
55
|
self.app.copy_folders_and_hidden_files(self.app_templates_root_dir, dest)
|
44
56
|
|
45
57
|
with open(os.path.join(dest, '.gitignore'), 'w') as fp:
|
46
|
-
fp.write("\n".join(
|
58
|
+
fp.write("\n".join(
|
59
|
+
[os.path.join(CORE_GATEWAY_SERVICE_NAME, '.docker-compose.base.yml'), '**/.env']
|
60
|
+
))
|
61
|
+
|
62
|
+
self.app_config.write()
|
63
|
+
|
64
|
+
# Provide plugins
|
65
|
+
warnings = []
|
66
|
+
if PLUGIN_CYPRESS in self.app_plugins:
|
67
|
+
self.__plugin_cypress(dest, PLUGIN_CYPRESS)
|
68
|
+
|
69
|
+
if not self.__cypress_initialized(self.app):
|
70
|
+
warnings.append(f"missing cypress.config.(js|ts), please run `npx cypress open` in {self.app.context_dir_path}")
|
71
|
+
|
72
|
+
|
73
|
+
if PLUGIN_PLAYWRIGHT in self.app_plugins:
|
74
|
+
self.__plugin_playwright(dest, PLUGIN_PLAYWRIGHT)
|
75
|
+
|
76
|
+
if not self.__playwright_initialized(self.app):
|
77
|
+
warnings.append(f"missing playwright.config.(js|ts), please run `npm init playwright@latest` in {self.app.context_dir_path}")
|
78
|
+
|
79
|
+
return {
|
80
|
+
'warnings': warnings
|
81
|
+
}
|
82
|
+
|
83
|
+
def __cypress_initialized(self, app: App):
|
84
|
+
if os.path.exists(os.path.join(app.context_dir_path, 'cypress.config.js')):
|
85
|
+
return True
|
86
|
+
|
87
|
+
if os.path.exists(os.path.join(app.context_dir_path, 'cypress.config.ts')):
|
88
|
+
return True
|
89
|
+
|
90
|
+
return False
|
91
|
+
|
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
|
+
def __playwright_initialized(self, app: App):
|
112
|
+
if os.path.exists(os.path.join(app.context_dir_path, 'playwright.config.js')):
|
113
|
+
return True
|
114
|
+
|
115
|
+
if os.path.exists(os.path.join(app.context_dir_path, 'playwright.config.ts')):
|
116
|
+
return True
|
117
|
+
|
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)
|
47
147
|
|
48
|
-
|
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)
|
@@ -9,6 +9,8 @@ APP_DIR_ENV = 'APP_DIR'
|
|
9
9
|
APP_DOCKER_SOCKET_PATH_ENV = 'APP_DOCKER_SOCKET_PATH'
|
10
10
|
APP_NETWORK_ENV = 'APP_NETWORK'
|
11
11
|
APP_NAME_ENV = 'APP_NAME'
|
12
|
+
APP_PLUGINS_ENV = 'APP_PLUGINS'
|
13
|
+
APP_PLUGINS_DELMITTER = ','
|
12
14
|
APP_UI_PORT_ENV = 'APP_UI_PORT'
|
13
15
|
BIN_FOLDER_NAME = 'bin'
|
14
16
|
CA_CERTS_DIR_ENV = 'CA_CERTS_DIR'
|
@@ -16,10 +18,12 @@ CERTS_DIR_ENV = 'CERTS_DIR'
|
|
16
18
|
COMPOSE_TEMPLATE = '.docker-compose.{workflow}.yml'
|
17
19
|
CONFIG_FILE = '.config.yml'
|
18
20
|
CONTEXT_DIR_ENV = 'CONTEXT_DIR'
|
19
|
-
DOCKER_NAMESPACE = 'docker'
|
20
21
|
DOTENV_FILE = '.env'
|
21
22
|
DOTENV_PATH_ENV = 'STOOBLY_DOTENV_PATH'
|
22
23
|
NAMESERVERS_FILE = '.nameservers'
|
24
|
+
PLUGIN_CYPRESS = 'cypress'
|
25
|
+
PLUGIN_PLAYWRIGHT = 'playwright'
|
26
|
+
PLUGINS_FOLDER = 'plugins'
|
23
27
|
PUBLIC_FOLDER_NAME = 'public'
|
24
28
|
SERVICE_DETACHED = '${SERVICE_DETACHED}'
|
25
29
|
SERVICE_DETACHED_ENV = 'SERVICE_DETACHED'
|
@@ -29,10 +33,9 @@ SERVICE_HOSTNAME = '${SERVICE_HOSTNAME}'
|
|
29
33
|
SERVICE_HOSTNAME_ENV = 'SERVICE_HOSTNAME'
|
30
34
|
SERVICE_ID = '${SERVICE_ID}'
|
31
35
|
SERVICE_ID_ENV = 'SERVICE_ID'
|
36
|
+
SERVICE_LOCAL_ENV = 'SERVICE_LOCAL'
|
32
37
|
SERVICE_NAME = '${SERVICE_NAME}'
|
33
38
|
SERVICE_NAME_ENV = 'SERVICE_NAME'
|
34
|
-
SERVICE_PROXY_MODE = '${SERVICE_PROXY_MODE}'
|
35
|
-
SERVICE_PROXY_MODE_ENV = 'SERVICE_PROXY_MODE'
|
36
39
|
SERVICE_SCHEME = '${SERVICE_SCHEME}'
|
37
40
|
SERVICE_SCHEME_ENV = 'SERVICE_SCHEME'
|
38
41
|
SERVICE_PORT = '${SERVICE_PORT}'
|
@@ -41,10 +44,18 @@ SERVICE_PRIORITY_ENV = 'SERVICE_PRIORITY'
|
|
41
44
|
SERVICE_SCRIPTS = '${SERVICE_SCRIPTS}'
|
42
45
|
SERVICE_SCRIPTS_DIR = '/usr/local/bin/services'
|
43
46
|
SERVICE_SCRIPTS_ENV = 'SERVICE_SCRIPTS'
|
47
|
+
SERVICE_UPSTREAM_HOSTNAME = '${SERVICE_UPSTREAM_HOSTNAME}'
|
48
|
+
SERVICE_UPSTREAM_HOSTNAME_ENV = 'SERVICE_UPSTREAM_HOSTNAME'
|
49
|
+
SERVICE_UPSTREAM_PORT = '${SERVICE_UPSTREAM_PORT}'
|
50
|
+
SERVICE_UPSTREAM_PORT_ENV = 'SERVICE_UPSTREAM_PORT'
|
51
|
+
SERVICE_UPSTREAM_SCHEME = '${SERVICE_UPSTREAM_SCHEME}'
|
52
|
+
SERVICE_UPSTREAM_SCHEME_ENV = 'SERVICE_UPSTREAM_SCHEME'
|
53
|
+
SERVICES_NAMESPACE = 'services'
|
44
54
|
STOOBLY_HOME_DIR = '/home/stoobly'
|
45
55
|
STOOBLY_DATA_DIR = os.path.join(STOOBLY_HOME_DIR, DATA_DIR_NAME)
|
46
56
|
STOOBLY_CERTS_DIR = os.path.join(STOOBLY_DATA_DIR, CERTS_DIR_NAME)
|
47
57
|
USER_ID_ENV = 'USER_ID'
|
58
|
+
WORKFLOW_CONTAINER_BRIDGE = 'bridge'
|
48
59
|
WORKFLOW_CONTAINER_CONFIGURE = 'configure'
|
49
60
|
WORKFLOW_CONTAINER_INIT = 'init'
|
50
61
|
WORKFLOW_CONTAINER_PROXY = 'proxy'
|
@@ -8,11 +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
|
+
DOCKER_COMPOSE_WORKFLOW_TEMPLATE = '.docker-compose.{workflow}.yml'
|
11
12
|
DOCKERFILE_CONTEXT = '.Dockerfile.context'
|
12
13
|
DOCKERFILE_SERVICE = 'Dockerfile.source'
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
# Example:
|
17
|
-
# COMPOSE_TEMPLATE = 'docker-compose.{workflow}.yml'
|
18
|
-
|
14
|
+
PLUGIN_CONTAINER_SERVICE_TEMPLATE = '{service}.{plugin}'
|
15
|
+
PLUGIN_DOCKER_ENTRYPOINT = '.entrypoint.sh'
|
16
|
+
PLUGIN_DOCKERFILE_TEMPLATE = '.Dockerfile.{plugin}'
|
@@ -2,7 +2,7 @@ import pdb
|
|
2
2
|
|
3
3
|
from typing import TypedDict
|
4
4
|
|
5
|
-
from ...constants import
|
5
|
+
from ...constants import SERVICES_NAMESPACE
|
6
6
|
from ..constants import DOCKERFILE_SERVICE
|
7
7
|
from .builder import ServiceBuilder
|
8
8
|
from .types import BuildDecoratorOptions
|
@@ -16,7 +16,7 @@ class BuildDecorator():
|
|
16
16
|
service_builder = self.__service_builder
|
17
17
|
build = {
|
18
18
|
'context': '../..', # Assumes app root is 2 levels up
|
19
|
-
'dockerfile': f"./{
|
19
|
+
'dockerfile': f"./{SERVICES_NAMESPACE}/{service_builder.service_name}/{DOCKERFILE_SERVICE}"
|
20
20
|
}
|
21
21
|
|
22
22
|
if 'build_args' in kwargs:
|
@@ -7,12 +7,13 @@ from stoobly_agent.config.data_dir import DATA_DIR_NAME
|
|
7
7
|
|
8
8
|
from ...app_config import AppConfig
|
9
9
|
from ...constants import (
|
10
|
-
APP_DIR,
|
10
|
+
APP_DIR, SERVICES_NAMESPACE,
|
11
11
|
SERVICE_HOSTNAME, SERVICE_HOSTNAME_ENV,
|
12
12
|
SERVICE_NAME, SERVICE_NAME_ENV,
|
13
13
|
SERVICE_ID,
|
14
14
|
SERVICE_PORT, SERVICE_PORT_ENV,
|
15
|
-
SERVICE_SCHEME, SERVICE_SCHEME_ENV,
|
15
|
+
SERVICE_SCHEME, SERVICE_SCHEME_ENV,
|
16
|
+
SERVICE_UPSTREAM_HOSTNAME, SERVICE_UPSTREAM_HOSTNAME_ENV, SERVICE_UPSTREAM_PORT, SERVICE_UPSTREAM_PORT_ENV, SERVICE_UPSTREAM_SCHEME, SERVICE_UPSTREAM_SCHEME_ENV,
|
16
17
|
STOOBLY_HOME_DIR, STOOBLY_HOME_DIR,
|
17
18
|
WORKFLOW_NAME, WORKFLOW_NAME_ENV, WORKFLOW_SCRIPTS, WORKFLOW_TEMPLATE
|
18
19
|
)
|
@@ -33,10 +34,11 @@ class ServiceBuilder(Builder):
|
|
33
34
|
self.app_builder = app_builder
|
34
35
|
|
35
36
|
self.__config = config
|
37
|
+
self.__upstream_port = None
|
36
38
|
self.__env = [SERVICE_NAME_ENV, WORKFLOW_NAME_ENV]
|
37
39
|
self.__service_name = os.path.basename(service_path)
|
38
40
|
self.__working_dir = os.path.join(
|
39
|
-
STOOBLY_HOME_DIR, DATA_DIR_NAME,
|
41
|
+
STOOBLY_HOME_DIR, DATA_DIR_NAME, SERVICES_NAMESPACE, SERVICE_NAME, WORKFLOW_NAME
|
40
42
|
)
|
41
43
|
|
42
44
|
@property
|
@@ -55,6 +57,10 @@ class ServiceBuilder(Builder):
|
|
55
57
|
def configure_base_service(self):
|
56
58
|
return self.services.get(self.configure_base)
|
57
59
|
|
60
|
+
@property
|
61
|
+
def upstream_port(self) -> int:
|
62
|
+
return self.__upstream_port
|
63
|
+
|
58
64
|
@property
|
59
65
|
def extends_service(self):
|
60
66
|
if self.config.detached:
|
@@ -99,9 +105,6 @@ class ServiceBuilder(Builder):
|
|
99
105
|
environment = { **self.env_dict() }
|
100
106
|
labels = [
|
101
107
|
'traefik.enable=true',
|
102
|
-
f"traefik.http.routers.{service_id}.rule=Host(`{SERVICE_HOSTNAME}`)",
|
103
|
-
f"traefik.http.routers.{service_id}.entrypoints={SERVICE_PORT}",
|
104
|
-
f"traefik.http.services.{service_id}.loadbalancer.server.port={SERVICE_PORT}"
|
105
108
|
]
|
106
109
|
volumes = []
|
107
110
|
|
@@ -109,7 +112,14 @@ class ServiceBuilder(Builder):
|
|
109
112
|
self.__with_detached_volumes(volumes)
|
110
113
|
|
111
114
|
if self.config.tls:
|
112
|
-
labels.append(f"traefik.
|
115
|
+
labels.append(f"traefik.tcp.routers.{service_id}.entrypoints={SERVICE_PORT}")
|
116
|
+
labels.append(f"traefik.tcp.routers.{service_id}.rule=HostSNI(`{SERVICE_HOSTNAME}`)")
|
117
|
+
labels.append(f"traefik.tcp.routers.{service_id}.tls.passthrough=true")
|
118
|
+
labels.append(f"traefik.tcp.services.{service_id}.loadbalancer.server.port={SERVICE_PORT}")
|
119
|
+
else:
|
120
|
+
labels.append(f"traefik.http.routers.{service_id}.entrypoints={SERVICE_PORT}")
|
121
|
+
labels.append(f"traefik.http.routers.{service_id}.rule=Host(`{SERVICE_HOSTNAME}`)")
|
122
|
+
labels.append(f"traefik.http.services.{service_id}.loadbalancer.server.port={SERVICE_PORT}")
|
113
123
|
|
114
124
|
base = {
|
115
125
|
'environment': environment,
|
@@ -178,6 +188,12 @@ class ServiceBuilder(Builder):
|
|
178
188
|
env[e] = '${' + e + '}'
|
179
189
|
return env
|
180
190
|
|
191
|
+
def with_upstream_port(self, v: int):
|
192
|
+
if not isinstance(v, int):
|
193
|
+
return self
|
194
|
+
self.__upstream_port = v
|
195
|
+
return self
|
196
|
+
|
181
197
|
def with_env(self, v: List[str]):
|
182
198
|
if not isinstance(v, list):
|
183
199
|
return self
|
@@ -205,13 +221,23 @@ class ServiceBuilder(Builder):
|
|
205
221
|
volumes.append(f"{self.service_name}:{STOOBLY_HOME_DIR}/{DATA_DIR_NAME}")
|
206
222
|
|
207
223
|
# Mount docker folder
|
208
|
-
volumes.append(f"../:{STOOBLY_HOME_DIR}/{DATA_DIR_NAME}/{
|
224
|
+
volumes.append(f"../:{STOOBLY_HOME_DIR}/{DATA_DIR_NAME}/{SERVICES_NAMESPACE}")
|
209
225
|
|
210
226
|
def __with_url_environment(self, environment):
|
211
|
-
|
227
|
+
if self.config.hostname:
|
228
|
+
environment[SERVICE_HOSTNAME_ENV] = SERVICE_HOSTNAME
|
212
229
|
|
213
230
|
if self.config.scheme:
|
214
231
|
environment[SERVICE_SCHEME_ENV] = SERVICE_SCHEME
|
215
232
|
|
216
233
|
if self.config.port:
|
217
|
-
environment[SERVICE_PORT_ENV] = SERVICE_PORT
|
234
|
+
environment[SERVICE_PORT_ENV] = SERVICE_PORT
|
235
|
+
|
236
|
+
if self.config.upstream_hostname:
|
237
|
+
environment[SERVICE_UPSTREAM_HOSTNAME_ENV] = SERVICE_UPSTREAM_HOSTNAME
|
238
|
+
|
239
|
+
if self.config.upstream_port:
|
240
|
+
environment[SERVICE_UPSTREAM_PORT_ENV] = SERVICE_UPSTREAM_PORT
|
241
|
+
|
242
|
+
if self.config.upstream_scheme:
|
243
|
+
environment[SERVICE_UPSTREAM_SCHEME_ENV] = SERVICE_UPSTREAM_SCHEME
|
@@ -94,7 +94,6 @@ class WorkflowBuilder(Builder):
|
|
94
94
|
if not self.service_builder.init_base_service:
|
95
95
|
return
|
96
96
|
|
97
|
-
return
|
98
97
|
service = {
|
99
98
|
'extends': self.service_builder.build_extends_init_base(self.dir_path),
|
100
99
|
'profiles': self.profiles,
|
@@ -138,10 +137,6 @@ class WorkflowBuilder(Builder):
|
|
138
137
|
}
|
139
138
|
|
140
139
|
if self.configure in self.services:
|
141
|
-
depends_on[self.init] = {
|
142
|
-
'condition': 'service_completed_successfully',
|
143
|
-
}
|
144
|
-
|
145
140
|
depends_on[self.configure] = {
|
146
141
|
'condition': 'service_completed_successfully',
|
147
142
|
}
|
@@ -158,19 +153,6 @@ class WorkflowBuilder(Builder):
|
|
158
153
|
env[e] = '${' + e + '}'
|
159
154
|
return env
|
160
155
|
|
161
|
-
def initialize_custom_file(self):
|
162
|
-
dest = self.custom_compose_file_path
|
163
|
-
|
164
|
-
if not os.path.exists(dest):
|
165
|
-
compose = {
|
166
|
-
'services': {}
|
167
|
-
}
|
168
|
-
|
169
|
-
if self.networks:
|
170
|
-
compose['networks'] = self.networks
|
171
|
-
|
172
|
-
super().write(compose, dest)
|
173
|
-
|
174
156
|
def write(self):
|
175
157
|
compose = {
|
176
158
|
'services': self.services,
|
@@ -183,12 +165,3 @@ class WorkflowBuilder(Builder):
|
|
183
165
|
compose['volumes'] = self.volumes
|
184
166
|
|
185
167
|
super().write(compose)
|
186
|
-
|
187
|
-
def __with_url_environment(self, environment):
|
188
|
-
environment[SERVICE_HOSTNAME_ENV] = SERVICE_HOSTNAME
|
189
|
-
|
190
|
-
if self.config.scheme:
|
191
|
-
environment[SERVICE_SCHEME_ENV] = SERVICE_SCHEME
|
192
|
-
|
193
|
-
if self.config.port:
|
194
|
-
environment[SERVICE_PORT_ENV] = SERVICE_PORT
|