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
@@ -0,0 +1,339 @@
|
|
1
|
+
import os
|
2
|
+
import pdb
|
3
|
+
import signal
|
4
|
+
import subprocess
|
5
|
+
import sys
|
6
|
+
|
7
|
+
from typing import Optional, List
|
8
|
+
|
9
|
+
from stoobly_agent.app.cli.scaffold.constants import PLUGIN_CYPRESS, PLUGIN_PLAYWRIGHT
|
10
|
+
from stoobly_agent.app.cli.scaffold.docker.constants import PLUGIN_CONTAINER_SERVICE_TEMPLATE
|
11
|
+
from stoobly_agent.app.cli.scaffold.templates.constants import CORE_BUILD_SERVICE_NAME, CORE_ENTRYPOINT_SERVICE_NAME, CUSTOM_CONFIGURE, CUSTOM_INIT, CUSTOM_RUN, MAINTAINED_CONFIGURE, MAINTAINED_INIT, MAINTAINED_RUN
|
12
|
+
from stoobly_agent.app.cli.scaffold.workflow_run_command import WorkflowRunCommand
|
13
|
+
from stoobly_agent.app.cli.types.workflow_run_command import WorkflowUpOptions, WorkflowDownOptions, WorkflowLogsOptions
|
14
|
+
from stoobly_agent.lib.logger import Logger
|
15
|
+
|
16
|
+
LOG_ID = 'LocalWorkflowRunCommand'
|
17
|
+
|
18
|
+
class LocalWorkflowRunCommand(WorkflowRunCommand):
|
19
|
+
"""Local workflow run command that executes stoobly-agent run directly."""
|
20
|
+
|
21
|
+
def __init__(self, app, services=None, script=None, **kwargs):
|
22
|
+
super().__init__(app, **kwargs)
|
23
|
+
|
24
|
+
self.services = services or []
|
25
|
+
self.script = script
|
26
|
+
|
27
|
+
self._log_file_path = None
|
28
|
+
self._pid_file_path = None
|
29
|
+
|
30
|
+
@property
|
31
|
+
def log_file_path(self):
|
32
|
+
"""Get the path to the PID file for this workflow."""
|
33
|
+
if not self._log_file_path:
|
34
|
+
self._log_file_path = os.path.join(self.workflow_namespace.path, f"{self.workflow_name}.log")
|
35
|
+
return self._log_file_path
|
36
|
+
|
37
|
+
@property
|
38
|
+
def pid_file_path(self):
|
39
|
+
"""Get the path to the PID file for this workflow."""
|
40
|
+
if not self._pid_file_path:
|
41
|
+
self._pid_file_path = os.path.join(self.workflow_namespace.path, f"{self.workflow_name}.pid")
|
42
|
+
return self._pid_file_path
|
43
|
+
|
44
|
+
def _write_pid(self, pid: int):
|
45
|
+
"""Write the process PID to the PID file."""
|
46
|
+
os.makedirs(os.path.dirname(self.pid_file_path), exist_ok=True)
|
47
|
+
with open(self.pid_file_path, 'w') as f:
|
48
|
+
f.write(str(pid))
|
49
|
+
|
50
|
+
def _read_pid(self) -> Optional[int]:
|
51
|
+
"""Read the process PID from the PID file."""
|
52
|
+
if not os.path.exists(self.pid_file_path):
|
53
|
+
return None
|
54
|
+
|
55
|
+
try:
|
56
|
+
with open(self.pid_file_path, 'r') as f:
|
57
|
+
return int(f.read().strip())
|
58
|
+
except (ValueError, IOError):
|
59
|
+
return None
|
60
|
+
|
61
|
+
def _kill_process(self, pid: int, signal_type=signal.SIGTERM):
|
62
|
+
"""Kill a process by PID."""
|
63
|
+
try:
|
64
|
+
os.kill(pid, signal_type)
|
65
|
+
return True
|
66
|
+
except (OSError, ProcessLookupError):
|
67
|
+
return False
|
68
|
+
|
69
|
+
def _is_process_running(self, pid: int) -> bool:
|
70
|
+
"""Check if a process is still running."""
|
71
|
+
try:
|
72
|
+
os.kill(pid, 0) # Signal 0 doesn't kill the process, just checks if it exists
|
73
|
+
return True
|
74
|
+
except (OSError, ProcessLookupError):
|
75
|
+
return False
|
76
|
+
|
77
|
+
def exec_service_script(self, service_name: str, step_script_path: str, args: List[str], cwd = None):
|
78
|
+
workflow_path = cwd or os.path.join(self.app.scaffold_namespace_path, service_name, self.workflow_name)
|
79
|
+
|
80
|
+
# Change directory to workflow path
|
81
|
+
command = [step_script_path] + args
|
82
|
+
if self.script:
|
83
|
+
command = [f"cd \"{workflow_path}\";"] + command
|
84
|
+
|
85
|
+
# Write the command to self.script_path
|
86
|
+
if self.script:
|
87
|
+
print(' '.join(command), file=self.script)
|
88
|
+
|
89
|
+
if self.dry_run:
|
90
|
+
print(' '.join(command))
|
91
|
+
else:
|
92
|
+
result = subprocess.run(['sh', '-c', ' '.join(command)], cwd=workflow_path)
|
93
|
+
if result.returncode != 0:
|
94
|
+
sys.exit(1)
|
95
|
+
|
96
|
+
def service_up(self, **options: WorkflowUpOptions):
|
97
|
+
print_service_header = options.get('print_service_header')
|
98
|
+
service_name = self.service_name
|
99
|
+
workflow_template = self.workflow_template
|
100
|
+
|
101
|
+
if print_service_header:
|
102
|
+
print_service_header(service_name)
|
103
|
+
|
104
|
+
self.write_env(**options)
|
105
|
+
|
106
|
+
# If service is build or entrypoint, use path in templates/build/services/SERVICE_NAME/.init
|
107
|
+
if service_name in [CORE_BUILD_SERVICE_NAME, CORE_ENTRYPOINT_SERVICE_NAME]:
|
108
|
+
init_script_path = os.path.join(self.service_templates_root_dir, service_name, workflow_template, MAINTAINED_INIT)
|
109
|
+
configure_script_path = os.path.join(self.service_templates_root_dir, service_name, workflow_template, MAINTAINED_CONFIGURE)
|
110
|
+
run_script_path = os.path.join(self.workflow_path, MAINTAINED_RUN)
|
111
|
+
|
112
|
+
if not os.path.exists(run_script_path):
|
113
|
+
run_script_path = os.path.join(self.service_templates_root_dir, service_name, workflow_template, MAINTAINED_RUN)
|
114
|
+
else:
|
115
|
+
# Absolute path to workflow .init script
|
116
|
+
# e.g. stoobly_agent/app/cli/scaffold/templates/build/workflows/record/.init
|
117
|
+
init_script_path = os.path.join(self.workflow_templates_build_dir, MAINTAINED_INIT)
|
118
|
+
|
119
|
+
# Absolute path to workflow .configure script
|
120
|
+
# e.g. stoobly_agent/app/cli/scaffold/templates/build/workflows/record/.configure
|
121
|
+
configure_script_path = os.path.join(self.workflow_templates_build_dir, MAINTAINED_CONFIGURE)
|
122
|
+
|
123
|
+
run_script_path = os.path.join(self.workflow_templates_build_dir, MAINTAINED_RUN)
|
124
|
+
|
125
|
+
self.exec_service_script(service_name, init_script_path, [CUSTOM_INIT])
|
126
|
+
self.exec_service_script(service_name, configure_script_path, [CUSTOM_CONFIGURE])
|
127
|
+
self.exec_service_script(service_name, run_script_path, [CUSTOM_RUN], cwd=os.getcwd())
|
128
|
+
|
129
|
+
def up(self, **options: WorkflowUpOptions):
|
130
|
+
"""Start the workflow using local stoobly-agent run."""
|
131
|
+
detached = options.get('detached', False)
|
132
|
+
|
133
|
+
commands = self.workflow_service_commands(**options)
|
134
|
+
|
135
|
+
# iterate through each service in the workflow
|
136
|
+
public_directory_paths = []
|
137
|
+
response_fixtures_paths = []
|
138
|
+
for command in commands:
|
139
|
+
url = command.service_config.url
|
140
|
+
if url:
|
141
|
+
if os.path.exists(command.public_dir_path):
|
142
|
+
public_directory_paths.append('--public-directory-path')
|
143
|
+
public_directory_paths.append(f"{command.public_dir_path}:{url}")
|
144
|
+
|
145
|
+
if os.path.exists(command.response_fixtures_path):
|
146
|
+
response_fixtures_paths.append('--response-fixtures-path')
|
147
|
+
response_fixtures_paths.append(f"{command.response_fixtures_path}:{url}")
|
148
|
+
|
149
|
+
# Check if PID file already exists
|
150
|
+
if os.path.exists(self.pid_file_path):
|
151
|
+
pid = self._read_pid()
|
152
|
+
if pid and self._is_process_running(pid):
|
153
|
+
Logger.instance(LOG_ID).error(f"Workflow {self.workflow_name} is already running with PID: {pid}")
|
154
|
+
Logger.instance(LOG_ID).error(f"Run `stoobly-agent scaffold workflow down {self.workflow_name}` to stop it first")
|
155
|
+
sys.exit(1)
|
156
|
+
else:
|
157
|
+
# PID file exists but process is not running, clean it up
|
158
|
+
os.remove(self.pid_file_path)
|
159
|
+
|
160
|
+
for command in commands:
|
161
|
+
command.service_up(**options)
|
162
|
+
|
163
|
+
# If second from last command, run up_command i.e. right before entrypoint
|
164
|
+
if command == commands[-2]:
|
165
|
+
self.__up_command(public_directory_paths, response_fixtures_paths, **options)
|
166
|
+
|
167
|
+
def down(self, **options: WorkflowDownOptions):
|
168
|
+
"""Stop the workflow by killing the local process."""
|
169
|
+
|
170
|
+
pid = self._read_pid()
|
171
|
+
if not pid:
|
172
|
+
Logger.instance(LOG_ID).warning(f"No PID file found for {self.workflow_name}")
|
173
|
+
return
|
174
|
+
|
175
|
+
if not self._is_process_running(pid):
|
176
|
+
Logger.instance(LOG_ID).info(f"Process {pid} for {self.workflow_name} is not running")
|
177
|
+
# Clean up PID file
|
178
|
+
if os.path.exists(self.pid_file_path):
|
179
|
+
os.remove(self.pid_file_path)
|
180
|
+
return
|
181
|
+
|
182
|
+
# Kill the process
|
183
|
+
if self.script:
|
184
|
+
self.__dry_run_down(pid, self.script)
|
185
|
+
|
186
|
+
if self.dry_run:
|
187
|
+
self.__dry_run_down(pid, sys.stdout)
|
188
|
+
else:
|
189
|
+
try:
|
190
|
+
# Try graceful shutdown first with SIGTERM
|
191
|
+
Logger.instance(LOG_ID).info(f"Sending SIGTERM to process {pid} for {self.workflow_name}")
|
192
|
+
self._kill_process(pid, signal.SIGTERM)
|
193
|
+
|
194
|
+
# Wait a bit for graceful shutdown
|
195
|
+
import time
|
196
|
+
time.sleep(2)
|
197
|
+
|
198
|
+
# Check if process still exists
|
199
|
+
if self._is_process_running(pid):
|
200
|
+
Logger.instance(LOG_ID).info(f"Process {pid} still running, sending SIGKILL")
|
201
|
+
self._kill_process(pid, signal.SIGKILL)
|
202
|
+
|
203
|
+
# Wait a bit more for SIGKILL to take effect
|
204
|
+
time.sleep(1)
|
205
|
+
|
206
|
+
# Final check
|
207
|
+
if self._is_process_running(pid):
|
208
|
+
Logger.instance(LOG_ID).warning(f"Process {pid} may still be running after SIGKILL")
|
209
|
+
else:
|
210
|
+
Logger.instance(LOG_ID).info(f"Successfully stopped process {pid} for {self.workflow_name}")
|
211
|
+
else:
|
212
|
+
Logger.instance(LOG_ID).info(f"Successfully stopped process {pid} for {self.workflow_name}")
|
213
|
+
|
214
|
+
# Clean up PID file
|
215
|
+
if os.path.exists(self.pid_file_path):
|
216
|
+
os.remove(self.pid_file_path)
|
217
|
+
|
218
|
+
except Exception as e:
|
219
|
+
Logger.instance(LOG_ID).error(f"Failed to stop {self.workflow_name}: {e}")
|
220
|
+
|
221
|
+
def logs(self, **options: WorkflowLogsOptions):
|
222
|
+
"""Show logs for the local workflow process."""
|
223
|
+
follow = options.get('follow', False)
|
224
|
+
|
225
|
+
pid = self._read_pid()
|
226
|
+
if not pid:
|
227
|
+
Logger.instance(LOG_ID).warning(f"No PID file found for {self.workflow_name}")
|
228
|
+
return
|
229
|
+
|
230
|
+
# Build log command
|
231
|
+
log_file = f"{self.log_file_path}"
|
232
|
+
if self.script:
|
233
|
+
self.__dry_run_logs(log_file, self.script, follow)
|
234
|
+
|
235
|
+
if self.dry_run:
|
236
|
+
self.__dry_run_logs(log_file, sys.stdout, follow)
|
237
|
+
else:
|
238
|
+
try:
|
239
|
+
if follow:
|
240
|
+
subprocess.run(['tail', '-f', log_file])
|
241
|
+
else:
|
242
|
+
subprocess.run(['cat', log_file])
|
243
|
+
except subprocess.CalledProcessError as e:
|
244
|
+
Logger.instance(LOG_ID).error(f"Failed to show logs for {self.workflow_name}: {e}")
|
245
|
+
|
246
|
+
def status(self):
|
247
|
+
"""Check the status of the local workflow process."""
|
248
|
+
pid = self._read_pid()
|
249
|
+
if not pid:
|
250
|
+
return "not running"
|
251
|
+
|
252
|
+
if self._is_process_running(pid):
|
253
|
+
return f"running (PID: {pid})"
|
254
|
+
else:
|
255
|
+
return "not running (stale PID file)"
|
256
|
+
|
257
|
+
def workflow_service_commands(self, **options: WorkflowUpOptions):
|
258
|
+
commands = list(map(lambda service_name: LocalWorkflowRunCommand(self.app, service_name=service_name, **options), self.services))
|
259
|
+
commands.sort(key=lambda command: command.service_config.priority)
|
260
|
+
return commands
|
261
|
+
|
262
|
+
def __dry_run_down(self, pid: int, output_file: str):
|
263
|
+
print(f"# Stop {self.workflow_name} (PID: {pid})", file=output_file)
|
264
|
+
print(f"kill {pid} || true", file=output_file)
|
265
|
+
print("sleep 1", file=output_file)
|
266
|
+
print(f"kill -0 {pid} 2>/dev/null && kill {pid} || true", file=output_file)
|
267
|
+
print(f"rm -f {self.pid_file_path}", file=output_file)
|
268
|
+
|
269
|
+
def __dry_run_logs(self, log_file: str, output_file: str, follow: bool):
|
270
|
+
print(f"# Show logs for {self.workflow_name}", file=output_file)
|
271
|
+
if follow:
|
272
|
+
print(f"tail -f {log_file}", file=output_file)
|
273
|
+
else:
|
274
|
+
print(f"cat {log_file}", file=output_file)
|
275
|
+
|
276
|
+
def __up_command(self, public_directory_paths: List[str], response_fixtures_paths: List[str], **options: WorkflowUpOptions):
|
277
|
+
# Build the stoobly-agent run command
|
278
|
+
command = ['stoobly-agent', 'run']
|
279
|
+
|
280
|
+
# Add log level if provided
|
281
|
+
if options.get('log_level'):
|
282
|
+
command.extend(['--log-level', options['log_level']])
|
283
|
+
|
284
|
+
# Use the PID file path as the detached output file
|
285
|
+
command.extend(['--detached', self.log_file_path])
|
286
|
+
|
287
|
+
command.extend(['--proxy-port', f"{self.app_config.proxy_port}"])
|
288
|
+
command.extend(['--ui-port', f"{self.app_config.ui_port}"])
|
289
|
+
|
290
|
+
if public_directory_paths:
|
291
|
+
command.extend(public_directory_paths)
|
292
|
+
|
293
|
+
if response_fixtures_paths:
|
294
|
+
command.extend(response_fixtures_paths)
|
295
|
+
|
296
|
+
# Convert command to string
|
297
|
+
command_str = ' '.join(command)
|
298
|
+
|
299
|
+
# Run in background using the main run command's --detached option
|
300
|
+
if self.script:
|
301
|
+
# Write to script for detached execution
|
302
|
+
script_lines = [
|
303
|
+
f"# Start {self.workflow_name} in background using --detached",
|
304
|
+
f"{command_str} > {self.pid_file_path}",
|
305
|
+
f"echo \"Started {self.workflow_name} with PID: $(cat {self.pid_file_path})\""
|
306
|
+
]
|
307
|
+
for line in script_lines:
|
308
|
+
print(line, file=self.script)
|
309
|
+
|
310
|
+
if self.dry_run:
|
311
|
+
print(command_str)
|
312
|
+
else:
|
313
|
+
# Execute directly
|
314
|
+
try:
|
315
|
+
# Run the command with --detached option
|
316
|
+
result = subprocess.run(
|
317
|
+
command,
|
318
|
+
capture_output=True,
|
319
|
+
text=True,
|
320
|
+
check=True
|
321
|
+
)
|
322
|
+
|
323
|
+
if result.returncode != 0:
|
324
|
+
Logger.instance(LOG_ID).error(f"Failed to start agent, run `stoobly-agent workflow logs {self.workflow_name}` to see the error")
|
325
|
+
sys.exit(1)
|
326
|
+
|
327
|
+
# The --detached option prints the PID to stdout
|
328
|
+
pid = int(result.stdout.strip())
|
329
|
+
|
330
|
+
# Write PID to file
|
331
|
+
self._write_pid(pid)
|
332
|
+
|
333
|
+
Logger.instance(LOG_ID).info(f"Started {self.workflow_name} with PID: {pid}")
|
334
|
+
except subprocess.CalledProcessError as e:
|
335
|
+
Logger.instance(LOG_ID).error(f"Failed to start {self.workflow_name}: {e}")
|
336
|
+
return None
|
337
|
+
except ValueError as e:
|
338
|
+
Logger.instance(LOG_ID).error(f"Failed to parse PID from output: {e}")
|
339
|
+
return None
|
@@ -12,10 +12,14 @@ class ServiceCommand(AppCommand):
|
|
12
12
|
super().__init__(app)
|
13
13
|
self.__service_name = kwargs.get('service_name')
|
14
14
|
|
15
|
-
|
15
|
+
if kwargs.get('service_name'):
|
16
|
+
self.__config = ServiceConfig(self.service_path, **kwargs)
|
16
17
|
|
17
18
|
@property
|
18
19
|
def service_config(self):
|
20
|
+
if not self.service_name:
|
21
|
+
raise Exception("Service name is required")
|
22
|
+
|
19
23
|
return self.__config
|
20
24
|
|
21
25
|
@service_config.setter
|
@@ -52,6 +56,10 @@ class ServiceCommand(AppCommand):
|
|
52
56
|
self.service_name,
|
53
57
|
)
|
54
58
|
|
59
|
+
@property
|
60
|
+
def service_templates_root_dir(self):
|
61
|
+
return os.path.join(self.templates_root_dir, 'build', 'services')
|
62
|
+
|
55
63
|
def config(self, _c: dict):
|
56
64
|
_config = self.app_config.read()
|
57
65
|
_config.update(self.service_config.read())
|
@@ -1,5 +1,6 @@
|
|
1
1
|
# Wraps the .config.yml file in the service folder
|
2
2
|
import hashlib
|
3
|
+
import os
|
3
4
|
import pdb
|
4
5
|
import re
|
5
6
|
|
@@ -68,6 +69,10 @@ class ServiceConfig(Config):
|
|
68
69
|
if 'upstream_scheme' in kwargs:
|
69
70
|
self.__upstream_scheme = kwargs.get('upstream_scheme')
|
70
71
|
|
72
|
+
@property
|
73
|
+
def app_config_dir_path(self):
|
74
|
+
return os.path.dirname(self.dir)
|
75
|
+
|
71
76
|
@property
|
72
77
|
def detached(self) -> bool:
|
73
78
|
return not not self.__detached
|
@@ -204,6 +209,9 @@ class ServiceConfig(Config):
|
|
204
209
|
|
205
210
|
@property
|
206
211
|
def url(self):
|
212
|
+
if not self.hostname:
|
213
|
+
return ''
|
214
|
+
|
207
215
|
_url = f"{self.scheme}://{self.hostname}"
|
208
216
|
|
209
217
|
if not self.port:
|
@@ -3,10 +3,12 @@ import pdb
|
|
3
3
|
import shutil
|
4
4
|
|
5
5
|
from copy import deepcopy
|
6
|
+
from typing import Union
|
6
7
|
|
7
8
|
from .app import App
|
8
|
-
from .constants import WORKFLOW_MOCK_TYPE, WORKFLOW_RECORD_TYPE, WORKFLOW_TEST_TYPE
|
9
|
-
from .
|
9
|
+
from .constants import RUN_ON_DOCKER, WORKFLOW_MOCK_TYPE, WORKFLOW_RECORD_TYPE, WORKFLOW_TEST_TYPE
|
10
|
+
from .local.service.builder import ServiceBuilder
|
11
|
+
from .docker.service.builder import DockerServiceBuilder
|
10
12
|
from .docker.workflow.decorators_factory import get_workflow_decorators
|
11
13
|
from .service_command import ServiceCommand
|
12
14
|
from .workflow_create_command import WorkflowCreateCommand
|
@@ -32,8 +34,18 @@ class ServiceCreateCommand(ServiceCommand):
|
|
32
34
|
def workflows(self):
|
33
35
|
return self.__workflows
|
34
36
|
|
37
|
+
@property
|
38
|
+
def create_docker_files(self):
|
39
|
+
"""Determine if Docker files should be created based on app config run-on setting."""
|
40
|
+
return RUN_ON_DOCKER in self.app_config.run_on
|
41
|
+
|
35
42
|
def build(self):
|
36
|
-
|
43
|
+
# Choose builder based on app run_on configuration
|
44
|
+
if RUN_ON_DOCKER in self.app_config.run_on:
|
45
|
+
service_builder = DockerServiceBuilder(self.service_config)
|
46
|
+
else:
|
47
|
+
service_builder = ServiceBuilder(self.service_config)
|
48
|
+
|
37
49
|
service_builder.with_env(list(self.env_vars))
|
38
50
|
service_decorators = []
|
39
51
|
|
@@ -67,19 +79,19 @@ class ServiceCreateCommand(ServiceCommand):
|
|
67
79
|
if os.path.exists(dest):
|
68
80
|
shutil.rmtree(dest)
|
69
81
|
|
70
|
-
def __build_with_mock_workflow(self, service_builder: ServiceBuilder, **kwargs):
|
82
|
+
def __build_with_mock_workflow(self, service_builder: Union[ServiceBuilder, DockerServiceBuilder], **kwargs):
|
71
83
|
mock_workflow = WorkflowCreateCommand(self.app, **{ **kwargs, **{ 'workflow_name': WORKFLOW_MOCK_TYPE }})
|
72
84
|
|
73
85
|
workflow_decorators = get_workflow_decorators(WORKFLOW_MOCK_TYPE, self.service_config)
|
74
86
|
mock_workflow.build(service_builder=service_builder, workflow_decorators=workflow_decorators)
|
75
87
|
|
76
|
-
def __build_with_record_workflow(self, service_builder: ServiceBuilder, **kwargs):
|
88
|
+
def __build_with_record_workflow(self, service_builder: Union[ServiceBuilder, DockerServiceBuilder], **kwargs):
|
77
89
|
record_workflow = WorkflowCreateCommand(self.app, **{ **kwargs, **{ 'workflow_name': WORKFLOW_RECORD_TYPE }})
|
78
90
|
|
79
91
|
workflow_decorators = get_workflow_decorators(WORKFLOW_RECORD_TYPE, self.service_config)
|
80
92
|
record_workflow.build(service_builder=service_builder, workflow_decorators=workflow_decorators)
|
81
93
|
|
82
|
-
def __build_with_test_workflow(self, service_builder: ServiceBuilder, **kwargs):
|
94
|
+
def __build_with_test_workflow(self, service_builder: Union[ServiceBuilder, DockerServiceBuilder], **kwargs):
|
83
95
|
mock_workflow = WorkflowCreateCommand(self.app, **{ **kwargs, **{ 'workflow_name': WORKFLOW_TEST_TYPE }})
|
84
96
|
|
85
97
|
workflow_decorators = get_workflow_decorators(WORKFLOW_TEST_TYPE, self.service_config)
|
@@ -12,4 +12,4 @@ class ServiceDockerCompose():
|
|
12
12
|
|
13
13
|
data_dir_path = DataDir.instance(app_dir_path).path
|
14
14
|
self.docker_compose_path = f"{data_dir_path}/{SERVICES_NAMESPACE}/{service_name}/{target_workflow_name}/docker-compose.yml"
|
15
|
-
self.init_script_path = f"{data_dir_path}/{SERVICES_NAMESPACE}/{service_name}/{target_workflow_name}/
|
15
|
+
self.init_script_path = f"{data_dir_path}/{SERVICES_NAMESPACE}/{service_name}/{target_workflow_name}/init"
|
@@ -33,10 +33,10 @@ workflow=record
|
|
33
33
|
workflow_service_options=$(shell echo $$STOOBLY_WORKFLOW_SERVICE_OPTIONS)
|
34
34
|
|
35
35
|
app_data_dir=$(app_dir)/.stoobly
|
36
|
-
app_namespace_dir=$(app_data_dir)/
|
36
|
+
app_namespace_dir=$(app_data_dir)/services
|
37
37
|
app_tmp_dir=$(app_data_dir)/tmp
|
38
38
|
dockerfile_path=$(app_namespace_dir)/.Dockerfile.context
|
39
|
-
exec_docker_compose_file_path=$(app_namespace_dir)/stoobly-ui/exec/.docker-compose.
|
39
|
+
exec_docker_compose_file_path=$(app_namespace_dir)/stoobly-ui/exec/.docker-compose.yml
|
40
40
|
workflow_namespace=$(if $(namespace),$(namespace),$(workflow))
|
41
41
|
workflow_namespace_dir=$(app_tmp_dir)/$(workflow_namespace)
|
42
42
|
workflow_script=.stoobly/tmp/$(workflow_namespace)/run.sh
|
@@ -2,7 +2,7 @@ services:
|
|
2
2
|
build.configure_base:
|
3
3
|
command:
|
4
4
|
- ${SERVICE_SCRIPTS}/${SERVICE_NAME}/${WORKFLOW_TEMPLATE}/.configure
|
5
|
-
-
|
5
|
+
- configure
|
6
6
|
extends:
|
7
7
|
file: ../.docker-compose.base.yml
|
8
8
|
service: context_base
|
@@ -10,7 +10,7 @@ services:
|
|
10
10
|
build.init_base:
|
11
11
|
command:
|
12
12
|
- ${SERVICE_SCRIPTS}/${SERVICE_NAME}/${WORKFLOW_TEMPLATE}/.init
|
13
|
-
-
|
13
|
+
- init
|
14
14
|
extends:
|
15
15
|
file: ../.docker-compose.base.yml
|
16
16
|
service: context_base
|
@@ -2,7 +2,7 @@ services:
|
|
2
2
|
entrypoint.configure_base:
|
3
3
|
command:
|
4
4
|
- ${SERVICE_SCRIPTS}/${SERVICE_NAME}/${WORKFLOW_TEMPLATE}/.configure
|
5
|
-
-
|
5
|
+
- configure
|
6
6
|
extends:
|
7
7
|
file: ../.docker-compose.base.yml
|
8
8
|
service: context_base
|
@@ -10,7 +10,7 @@ services:
|
|
10
10
|
entrypoint.init_base:
|
11
11
|
command:
|
12
12
|
- ${SERVICE_SCRIPTS}/${SERVICE_NAME}/${WORKFLOW_TEMPLATE}/.init
|
13
|
-
-
|
13
|
+
- init
|
14
14
|
extends:
|
15
15
|
file: ../.docker-compose.base.yml
|
16
16
|
service: context_base
|
@@ -3,6 +3,10 @@
|
|
3
3
|
# This file was automatically generated. DO NOT EDIT.
|
4
4
|
# Any changes made to this file will be overwritten.
|
5
5
|
|
6
|
+
if [ -f .env ]; then
|
7
|
+
set -a; . ./.env; set +a;
|
8
|
+
fi
|
9
|
+
|
6
10
|
echo "Configuring intercept..."
|
7
11
|
stoobly-agent intercept configure --mode mock --policy all
|
8
12
|
|
@@ -12,5 +16,5 @@ stoobly-agent intercept enable
|
|
12
16
|
entrypoint=$1
|
13
17
|
|
14
18
|
if [ -e "$entrypoint" ]; then
|
15
|
-
"$entrypoint"
|
19
|
+
./"$entrypoint"
|
16
20
|
fi
|
@@ -3,6 +3,10 @@
|
|
3
3
|
# This file was automatically generated. DO NOT EDIT.
|
4
4
|
# Any changes made to this file will be overwritten.
|
5
5
|
|
6
|
+
if [ -f .env ]; then
|
7
|
+
set -a; . ./.env; set +a;
|
8
|
+
fi
|
9
|
+
|
6
10
|
stoobly-agent config reset
|
7
11
|
|
8
12
|
stoobly-agent snapshot apply
|
@@ -10,5 +14,5 @@ stoobly-agent snapshot apply
|
|
10
14
|
entrypoint=$1
|
11
15
|
|
12
16
|
if [ -e "$entrypoint" ]; then
|
13
|
-
"$entrypoint" /app
|
17
|
+
./"$entrypoint" /app
|
14
18
|
fi
|
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/bin/bash
|
2
|
+
|
3
|
+
# This file was automatically generated. DO NOT EDIT.
|
4
|
+
# Any changes made to this file will be overwritten.
|
5
|
+
|
6
|
+
if [ -f .env ]; then
|
7
|
+
set -a; . ./.env; set +a;
|
8
|
+
fi
|
9
|
+
|
10
|
+
entrypoint=$1
|
11
|
+
|
12
|
+
if [ -e "$entrypoint" ]; then
|
13
|
+
./"$entrypoint"
|
14
|
+
fi
|
@@ -3,11 +3,15 @@
|
|
3
3
|
# This file was automatically generated. DO NOT EDIT.
|
4
4
|
# Any changes made to this file will be overwritten.
|
5
5
|
|
6
|
+
if [ -f .env ]; then
|
7
|
+
set -a; . ./.env; set +a;
|
8
|
+
fi
|
9
|
+
|
6
10
|
echo "Configuring intercept..."
|
7
11
|
stoobly-agent intercept configure --mode record --policy all
|
8
12
|
|
9
13
|
entrypoint=$1
|
10
14
|
|
11
15
|
if [ -e "$entrypoint" ]; then
|
12
|
-
"$entrypoint"
|
16
|
+
./"$entrypoint"
|
13
17
|
fi
|
@@ -3,6 +3,10 @@
|
|
3
3
|
# This file was automatically generated. DO NOT EDIT.
|
4
4
|
# Any changes made to this file will be overwritten.
|
5
5
|
|
6
|
+
if [ -f .env ]; then
|
7
|
+
set -a; . ./.env; set +a;
|
8
|
+
fi
|
9
|
+
|
6
10
|
stoobly-agent config reset
|
7
11
|
|
8
12
|
stoobly-agent snapshot apply
|
@@ -10,5 +14,5 @@ stoobly-agent snapshot apply
|
|
10
14
|
entrypoint=$1
|
11
15
|
|
12
16
|
if [ -e "$entrypoint" ]; then
|
13
|
-
"$entrypoint" /app
|
17
|
+
./"$entrypoint" /app
|
14
18
|
fi
|
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/bin/bash
|
2
|
+
|
3
|
+
# This file was automatically generated. DO NOT EDIT.
|
4
|
+
# Any changes made to this file will be overwritten.
|
5
|
+
|
6
|
+
if [ -f .env ]; then
|
7
|
+
set -a; . ./.env; set +a;
|
8
|
+
fi
|
9
|
+
|
10
|
+
entrypoint=$1
|
11
|
+
|
12
|
+
if [ -e "$entrypoint" ]; then
|
13
|
+
./"$entrypoint"
|
14
|
+
fi
|
@@ -3,6 +3,10 @@
|
|
3
3
|
# This file was automatically generated. DO NOT EDIT.
|
4
4
|
# Any changes made to this file will be overwritten.
|
5
5
|
|
6
|
+
if [ -f .env ]; then
|
7
|
+
set -a; . ./.env; set +a;
|
8
|
+
fi
|
9
|
+
|
6
10
|
echo "Configuring intercept..."
|
7
11
|
stoobly-agent intercept configure --mode mock --policy all
|
8
12
|
|
@@ -12,5 +16,5 @@ stoobly-agent intercept enable
|
|
12
16
|
entrypoint=$1
|
13
17
|
|
14
18
|
if [ -e "$entrypoint" ]; then
|
15
|
-
"$entrypoint"
|
19
|
+
./"$entrypoint"
|
16
20
|
fi
|
@@ -3,6 +3,10 @@
|
|
3
3
|
# This file was automatically generated. DO NOT EDIT.
|
4
4
|
# Any changes made to this file will be overwritten.
|
5
5
|
|
6
|
+
if [ -f .env ]; then
|
7
|
+
set -a; . ./.env; set +a;
|
8
|
+
fi
|
9
|
+
|
6
10
|
stoobly-agent config reset
|
7
11
|
|
8
12
|
stoobly-agent snapshot apply
|
@@ -10,5 +14,5 @@ stoobly-agent snapshot apply
|
|
10
14
|
entrypoint=$1
|
11
15
|
|
12
16
|
if [ -e "$entrypoint" ]; then
|
13
|
-
"$entrypoint" /app
|
17
|
+
./"$entrypoint" /app
|
14
18
|
fi
|
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/bin/bash
|
2
|
+
|
3
|
+
# This file was automatically generated. DO NOT EDIT.
|
4
|
+
# Any changes made to this file will be overwritten.
|
5
|
+
|
6
|
+
if [ -f .env ]; then
|
7
|
+
set -a; . ./.env; set +a;
|
8
|
+
fi
|
9
|
+
|
10
|
+
entrypoint=$1
|
11
|
+
|
12
|
+
if [ -e "$entrypoint" ]; then
|
13
|
+
./"$entrypoint"
|
14
|
+
fi
|