stoobly-agent 1.7.0__py3-none-any.whl → 1.7.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/cli/scaffold/hosts_file_manager.py +10 -10
- stoobly_agent/app/cli/scaffold/service_workflow_validate_command.py +48 -12
- stoobly_agent/app/cli/scaffold/templates/app/.Dockerfile.context +1 -1
- stoobly_agent/app/cli/scaffold/validate_command.py +31 -9
- stoobly_agent/app/cli/scaffold/workflow_validate_command.py +58 -33
- stoobly_agent/app/cli/scaffold_cli.py +7 -2
- stoobly_agent/test/app/cli/scaffold/e2e_test.py +3 -3
- stoobly_agent/test/app/cli/scaffold/hosts_file_manager_test.py +1 -1
- stoobly_agent/test/app/models/schemas/.stoobly/db/VERSION +1 -1
- {stoobly_agent-1.7.0.dist-info → stoobly_agent-1.7.1.dist-info}/METADATA +1 -1
- {stoobly_agent-1.7.0.dist-info → stoobly_agent-1.7.1.dist-info}/RECORD +15 -15
- {stoobly_agent-1.7.0.dist-info → stoobly_agent-1.7.1.dist-info}/LICENSE +0 -0
- {stoobly_agent-1.7.0.dist-info → stoobly_agent-1.7.1.dist-info}/WHEEL +0 -0
- {stoobly_agent-1.7.0.dist-info → stoobly_agent-1.7.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.7.
|
2
|
+
VERSION = '1.7.1'
|
@@ -15,21 +15,21 @@ class HostsFileManager():
|
|
15
15
|
ip_address: str
|
16
16
|
hostnames: list[str]
|
17
17
|
|
18
|
-
|
18
|
+
# Split IP address and hostnames. Don't include inline comments
|
19
|
+
def __split_hosts_line(self, line: str) -> list[str]:
|
20
|
+
ip_addr_hosts_split = line.split('#')[0].split()
|
21
|
+
return ip_addr_hosts_split
|
22
|
+
|
23
|
+
def get_hosts_file_path(self) -> str:
|
19
24
|
file_path = '/etc/hosts'
|
20
25
|
if not os.path.exists(file_path):
|
21
26
|
print(f"Error: File {file_path} not found.", file=sys.stderr)
|
22
27
|
sys.exit(1)
|
23
28
|
return file_path
|
24
29
|
|
25
|
-
# Split IP address and hostnames. Don't include inline comments
|
26
|
-
def __split_hosts_line(self, line: str) -> list[str]:
|
27
|
-
ip_addr_hosts_split = line.split('#')[0].split()
|
28
|
-
return ip_addr_hosts_split
|
29
|
-
|
30
30
|
# Parses hosts file and returns a mapping of IP address to hostnames in a list.
|
31
31
|
def get_hosts(self) -> list[IpAddressToHostnames]:
|
32
|
-
hosts_file_path = self.
|
32
|
+
hosts_file_path = self.get_hosts_file_path()
|
33
33
|
|
34
34
|
if not hosts_file_path:
|
35
35
|
return []
|
@@ -64,14 +64,14 @@ class HostsFileManager():
|
|
64
64
|
return None
|
65
65
|
|
66
66
|
def install_hostnames(self, hostnames: list[str]) -> None:
|
67
|
-
hosts_file_path = self.
|
67
|
+
hosts_file_path = self.get_hosts_file_path()
|
68
68
|
|
69
69
|
self.__add_lines_between_markers(
|
70
70
|
hosts_file_path, SCAFFOLD_HOSTS_DELIMITTER_BEGIN, SCAFFOLD_HOSTS_DELIMITTER_END, hostnames
|
71
71
|
)
|
72
72
|
|
73
73
|
def uninstall_hostnames(self, hostnames: list[str] = []) -> None:
|
74
|
-
hosts_file_path = self.
|
74
|
+
hosts_file_path = self.get_hosts_file_path()
|
75
75
|
|
76
76
|
self.__remove_lines_between_markers(
|
77
77
|
hosts_file_path, SCAFFOLD_HOSTS_DELIMITTER_BEGIN, SCAFFOLD_HOSTS_DELIMITTER_END, hostnames
|
@@ -182,4 +182,4 @@ class HostsFileManager():
|
|
182
182
|
filtered_lines.append(line)
|
183
183
|
|
184
184
|
with open(file_path, "w") as file:
|
185
|
-
file.writelines(filtered_lines)
|
185
|
+
file.writelines(filtered_lines)
|
@@ -4,6 +4,7 @@ import socket
|
|
4
4
|
import time
|
5
5
|
|
6
6
|
from collections import Counter
|
7
|
+
from docker import errors as docker_errors
|
7
8
|
from pathlib import Path
|
8
9
|
|
9
10
|
import yaml
|
@@ -25,6 +26,7 @@ from stoobly_agent.app.cli.scaffold.service_docker_compose import ServiceDockerC
|
|
25
26
|
from stoobly_agent.app.cli.scaffold.validate_command import ValidateCommand
|
26
27
|
from stoobly_agent.app.cli.scaffold.validate_exceptions import ScaffoldValidateException
|
27
28
|
from stoobly_agent.config.data_dir import DATA_DIR_NAME
|
29
|
+
from stoobly_agent.lib.logger import bcolors
|
28
30
|
|
29
31
|
from .app import App
|
30
32
|
|
@@ -84,7 +86,10 @@ class ServiceWorkflowValidateCommand(ServiceCommand, ValidateCommand):
|
|
84
86
|
if attempt < retries - 1:
|
85
87
|
time.sleep(delay)
|
86
88
|
|
87
|
-
|
89
|
+
hostname_not_reachable_message = f"{bcolors.FAIL}Connection failed to hostname: {hostname}, port: {port}, num_retries: {retries}{bcolors.ENDC}"
|
90
|
+
suggestion_message = f"{bcolors.BOLD}Try confirming that {hostname} is reachable from your machine or environment.{bcolors.ENDC}"
|
91
|
+
error_message = f"{hostname_not_reachable_message}\n\n{suggestion_message}"
|
92
|
+
raise ScaffoldValidateException(error_message)
|
88
93
|
|
89
94
|
# Check if hostname is defined in hosts file
|
90
95
|
def hostname_exists(self, hostname: str) -> bool:
|
@@ -96,7 +101,12 @@ class ServiceWorkflowValidateCommand(ServiceCommand, ValidateCommand):
|
|
96
101
|
print(f"Correct hosts mapping found for {hostname}")
|
97
102
|
return True
|
98
103
|
|
99
|
-
|
104
|
+
hosts_file_path = hosts_file_manager.get_hosts_file_path()
|
105
|
+
|
106
|
+
missing_host_message = f"{bcolors.FAIL}Missing hosts mapping for {hostname}{bcolors.ENDC}"
|
107
|
+
suggestion_message = f"{bcolors.BOLD}Confirm hostname '{hostname}' is in your hosts file at '{hosts_file_path}'. If not there, please add it{bcolors.ENDC}"
|
108
|
+
error_message = f"{missing_host_message}\n\n{suggestion_message}"
|
109
|
+
raise ScaffoldValidateException(error_message)
|
100
110
|
|
101
111
|
def validate_hostname(self, hostname: str, port: int) -> None:
|
102
112
|
print(f"Validating hostname: {hostname}")
|
@@ -188,8 +198,13 @@ class ServiceWorkflowValidateCommand(ServiceCommand, ValidateCommand):
|
|
188
198
|
|
189
199
|
def validate_proxy_container(self, service_proxy_container: Container):
|
190
200
|
print(f"Validating proxy container: {service_proxy_container.name}")
|
201
|
+
|
202
|
+
if service_proxy_container.status == 'exited' or service_proxy_container.attrs['State']['ExitCode'] != 0:
|
203
|
+
raise ScaffoldValidateException(f"Proxy container is exited: {service_proxy_container.name}")
|
204
|
+
if service_proxy_container.status != 'running':
|
205
|
+
raise ScaffoldValidateException(f"Proxy container is not running: {service_proxy_container.name}")
|
191
206
|
if not service_proxy_container.attrs:
|
192
|
-
raise ScaffoldValidateException(f"Container attributes are missing for: {
|
207
|
+
raise ScaffoldValidateException(f"Container attributes are missing for: {service_proxy_container.name}")
|
193
208
|
|
194
209
|
if not self.service_config.detached:
|
195
210
|
self.validate_public_folder(service_proxy_container)
|
@@ -219,32 +234,53 @@ class ServiceWorkflowValidateCommand(ServiceCommand, ValidateCommand):
|
|
219
234
|
self.validate_public_folder(init_container)
|
220
235
|
|
221
236
|
if self.service_config.hostname:
|
222
|
-
|
237
|
+
try:
|
238
|
+
service_proxy_container = self.docker_client.containers.get(self.service_docker_compose.proxy_container_name)
|
239
|
+
except docker_errors.NotFound:
|
240
|
+
error_message = self._ValidateCommand__generate_container_not_found_error(self.service_docker_compose.proxy_container_name)
|
241
|
+
raise ScaffoldValidateException(error_message)
|
242
|
+
|
223
243
|
self.validate_proxy_container(service_proxy_container)
|
224
244
|
|
245
|
+
# External services won't have a container to check
|
246
|
+
if self.is_local() or self.service_config.detached:
|
247
|
+
container_name = self.service_docker_compose.container_name
|
248
|
+
try:
|
249
|
+
service_container = self.docker_client.containers.get(container_name)
|
250
|
+
except docker_errors.NotFound:
|
251
|
+
error_message = self._ValidateCommand__generate_container_not_found_error(container_name)
|
252
|
+
raise ScaffoldValidateException(error_message)
|
253
|
+
if service_container.status == 'exited' or service_container.attrs['State']['ExitCode'] != 0:
|
254
|
+
raise ScaffoldValidateException(f"Custom container is exited: {service_container.name}")
|
255
|
+
if service_container.status != 'running':
|
256
|
+
raise ScaffoldValidateException(f"Custom container is not running: {service_container.name}")
|
257
|
+
|
225
258
|
if self.is_local():
|
226
259
|
print(f"Validating local user defined service: {self.service_name}")
|
227
260
|
# Validate docker-compose path exists
|
228
261
|
docker_compose_path = f"{self.app_dir_path}/{DATA_DIR_NAME}/docker/{self.service_docker_compose.service_name}/{self.workflow_name}/docker-compose.yml"
|
229
262
|
destination_path = Path(docker_compose_path)
|
263
|
+
|
264
|
+
if not destination_path.exists():
|
265
|
+
message = f"{bcolors.FAIL}Docker Compose file does not exist: {destination_path}{bcolors.ENDC}"
|
266
|
+
suggestion_message = f"{bcolors.BOLD}A missing Docker Compose file often means it got deleted by accident. Please restore from backup or rerun the scaffold service create command.{bcolors.ENDC}"
|
267
|
+
error_message = f"{message}\n\n{suggestion_message}"
|
268
|
+
raise ScaffoldValidateException(error_message)
|
230
269
|
if not destination_path.is_file():
|
231
270
|
raise ScaffoldValidateException(f"Docker compose path is not a file: {destination_path}")
|
232
271
|
|
233
272
|
# Validate docker-compose.yml file has the service defined
|
234
273
|
with open(destination_path) as f:
|
235
274
|
if self.service_name not in f.read():
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
return False
|
275
|
+
message = f"{bcolors.FAIL}Custom container service is not defined in Docker Compose file: {destination_path}{bcolors.ENDC}"
|
276
|
+
suggestion_message = f"{bcolors.BOLD}Please add your service definition. See https://docs.docker.com/reference/compose-file/services {bcolors.ENDC}"
|
277
|
+
error_message = f"{message}\n\n{suggestion_message}"
|
278
|
+
raise ScaffoldValidateException(error_message)
|
241
279
|
|
242
280
|
if self.service_config.detached:
|
243
|
-
service_container = self.docker_client.containers.get(self.service_docker_compose.container_name)
|
244
281
|
self.validate_detached(service_container)
|
245
282
|
|
246
|
-
print(f"Done validating service: {self.service_name}, success!")
|
247
|
-
print()
|
283
|
+
print(f"{bcolors.OKGREEN}✔ Done validating service: {self.service_name}, success!{bcolors.ENDC}\n")
|
248
284
|
|
249
285
|
return True
|
250
286
|
|
@@ -1,6 +1,7 @@
|
|
1
1
|
import pdb
|
2
2
|
import re
|
3
3
|
from time import sleep
|
4
|
+
from typing import Union
|
4
5
|
|
5
6
|
import docker
|
6
7
|
from docker import errors as docker_errors
|
@@ -8,14 +9,22 @@ from docker.models.containers import Container
|
|
8
9
|
|
9
10
|
from stoobly_agent.app.cli.scaffold.validate_exceptions import ScaffoldValidateException
|
10
11
|
from stoobly_agent.config.data_dir import DATA_DIR_NAME
|
12
|
+
from stoobly_agent.lib.logger import bcolors
|
11
13
|
|
12
14
|
|
13
15
|
class ValidateCommand():
|
14
16
|
def __init__(self):
|
15
17
|
self.docker_client = docker.from_env()
|
16
18
|
|
19
|
+
def __generate_container_not_found_error(self, container_name: Union[str, None]) -> str:
|
20
|
+
not_found_error_message = f"{bcolors.FAIL}Container not found: {container_name}{bcolors.ENDC}"
|
21
|
+
suggestion_message = f"{bcolors.BOLD}Run 'docker ps -a | grep {container_name}'. If found, then inspect the logs with 'docker logs {container_name}'{bcolors.ENDC}"
|
22
|
+
error_message = f"{not_found_error_message}\n\n{suggestion_message}"
|
23
|
+
|
24
|
+
return error_message
|
25
|
+
|
17
26
|
# Some containers like init and configure can take longer than expected to finish so retry
|
18
|
-
def
|
27
|
+
def __get_container_with_retries(self, container_name: str) -> Container:
|
19
28
|
tries = 30
|
20
29
|
for _ in range(tries):
|
21
30
|
try:
|
@@ -24,20 +33,28 @@ class ValidateCommand():
|
|
24
33
|
except docker_errors.NotFound:
|
25
34
|
sleep(0.5)
|
26
35
|
|
27
|
-
|
36
|
+
error_message = self.__generate_container_not_found_error(container_name)
|
37
|
+
raise ScaffoldValidateException(error_message)
|
28
38
|
|
29
39
|
def validate_init_containers(self, init_container_name, configure_container_name) -> None:
|
30
40
|
print(f"Validating setup containers: {init_container_name}, {configure_container_name}")
|
31
41
|
|
32
|
-
|
33
|
-
init_container = self.__get_container(init_container_name)
|
42
|
+
init_container = self.__get_container_with_retries(init_container_name)
|
34
43
|
logs = init_container.logs()
|
44
|
+
|
35
45
|
if logs and re.search('error', str(logs), re.IGNORECASE):
|
36
|
-
|
46
|
+
error_found_message = f"{bcolors.FAIL}Error logs potentially detected in: {init_container_name}{bcolors.ENDC}"
|
47
|
+
suggestion_message = f"{bcolors.BOLD}Run 'docker logs {init_container_name}'{bcolors.ENDC}"
|
48
|
+
error_message = f"{error_found_message}\n\n{suggestion_message}"
|
49
|
+
raise ScaffoldValidateException(error_message)
|
50
|
+
|
37
51
|
if init_container.status != 'exited' or init_container.attrs['State']['ExitCode'] != 0:
|
38
|
-
|
52
|
+
init_exit_message = f"{bcolors.FAIL}init container {init_container_name} exited with: {init_container.attrs['State']['ExitCode']}{bcolors.ENDC}"
|
53
|
+
suggestion_message = f"{bcolors.BOLD}Run 'docker logs {init_container_name}'{bcolors.ENDC}"
|
54
|
+
error_message = f"{init_exit_message}\n\n{suggestion_message}"
|
55
|
+
raise ScaffoldValidateException(error_message)
|
39
56
|
|
40
|
-
configure_container = self.
|
57
|
+
configure_container = self.__get_container_with_retries(configure_container_name)
|
41
58
|
|
42
59
|
configure_container_ran = False
|
43
60
|
if configure_container.status == 'exited' and configure_container.attrs['State']['ExitCode'] == 0:
|
@@ -49,12 +66,17 @@ class ValidateCommand():
|
|
49
66
|
print(f"Validating detached for: {container.name}")
|
50
67
|
|
51
68
|
if not container.attrs:
|
52
|
-
|
69
|
+
error_message = self.__generate_container_not_found_error(container.name)
|
70
|
+
raise ScaffoldValidateException(error_message)
|
53
71
|
|
54
72
|
volume_mounts = container.attrs['Mounts']
|
55
73
|
for volume_mount in volume_mounts:
|
56
74
|
if DATA_DIR_NAME in volume_mount['Source']:
|
57
75
|
return
|
76
|
+
|
77
|
+
message = f"{bcolors.FAIL}Data directory is missing from container: {container.name}{bcolors.ENDC}"
|
78
|
+
suggestion_message = f"{bcolors.BOLD}Data directory might have failed to mount{bcolors.ENDC}"
|
79
|
+
error_message = f"{message}\n\n{suggestion_message}"
|
58
80
|
|
59
|
-
raise ScaffoldValidateException(
|
81
|
+
raise ScaffoldValidateException(error_message)
|
60
82
|
|
@@ -15,6 +15,7 @@ from stoobly_agent.app.cli.scaffold.templates.constants import (
|
|
15
15
|
from stoobly_agent.app.cli.scaffold.validate_command import ValidateCommand
|
16
16
|
from stoobly_agent.app.cli.scaffold.validate_exceptions import ScaffoldValidateException
|
17
17
|
from stoobly_agent.app.cli.scaffold.workflow_command import WorkflowCommand
|
18
|
+
from stoobly_agent.lib.logger import bcolors
|
18
19
|
|
19
20
|
from .app import App
|
20
21
|
|
@@ -25,62 +26,86 @@ class WorkflowValidateCommand(WorkflowCommand, ValidateCommand):
|
|
25
26
|
ValidateCommand.__init__(self)
|
26
27
|
self.managed_services_docker_compose = ManagedServicesDockerCompose(target_workflow_name=self.workflow_name)
|
27
28
|
|
28
|
-
|
29
|
-
|
29
|
+
# Gateway core service runs in all workflows
|
30
|
+
def validate_gateway_service(self):
|
31
|
+
print(f"Validating core service: {CORE_GATEWAY_SERVICE_NAME}")
|
30
32
|
gateway_container_name = self.managed_services_docker_compose.gateway_container_name
|
31
|
-
gateway_container = self.docker_client.containers.get(gateway_container_name)
|
32
|
-
if not gateway_container or (gateway_container.status != 'running'):
|
33
|
-
raise ScaffoldValidateException(f"Container '{gateway_container_name}' not found for service '{CORE_GATEWAY_SERVICE_NAME}'")
|
34
33
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
if not mock_ui_container or (mock_ui_container.status != 'running'):
|
39
|
-
raise ScaffoldValidateException(f"Container '{mock_ui_container_name}' not found for service '{CORE_MOCK_UI_SERVICE_NAME}'")
|
34
|
+
container_missing_message = f"{bcolors.FAIL}Container '{gateway_container_name}' not found for service '{CORE_GATEWAY_SERVICE_NAME}'{bcolors.ENDC}"
|
35
|
+
suggestion_message = f"{bcolors.BOLD}Workflow might not be running yet. Try running 'scaffold workflow up <WORKFLOW_NAME>'{bcolors.ENDC}"
|
36
|
+
error_message = f"{container_missing_message}\n\n{suggestion_message}"
|
40
37
|
|
41
|
-
def validate_no_core_components(self):
|
42
38
|
try:
|
43
|
-
|
44
|
-
if
|
45
|
-
raise ScaffoldValidateException(
|
39
|
+
gateway_container = self.docker_client.containers.get(gateway_container_name)
|
40
|
+
if not gateway_container or (gateway_container.status != 'running'):
|
41
|
+
raise ScaffoldValidateException(error_message)
|
46
42
|
except docker_errors.NotFound:
|
47
|
-
|
48
|
-
|
49
|
-
print(f"Skipping validating core component: {CORE_GATEWAY_SERVICE_NAME}")
|
50
|
-
print(f"Skipping validating core component: {CORE_MOCK_UI_SERVICE_NAME}")
|
43
|
+
raise ScaffoldValidateException(error_message)
|
51
44
|
|
52
|
-
|
53
|
-
|
54
|
-
print(f"Validating workflow: {self.workflow_name}")
|
55
|
-
print(f"Validating core components: {CORE_SERVICES}")
|
45
|
+
def validate_mock_ui_service(self):
|
46
|
+
mock_ui_container_name = self.managed_services_docker_compose.mock_ui_container_name
|
56
47
|
|
48
|
+
# The stoobly-ui service does not run in test workflows
|
57
49
|
if self.workflow_name == WORKFLOW_TEST_TYPE:
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
50
|
+
try:
|
51
|
+
mock_ui_container = self.docker_client.containers.get(mock_ui_container_name)
|
52
|
+
|
53
|
+
if mock_ui_container:
|
54
|
+
ui_found_message = f"{bcolors.FAIL}Stoobly UI container is running when it shouldn't: {mock_ui_container_name}{bcolors.ENDC}"
|
55
|
+
suggestion_message = f"{bcolors.BOLD}Run 'docker ps | grep stoobly_ui' to check if the Stoobly UI is running during the test workflow. Did you stop the record or mock workflow yet?{bcolors.ENDC}"
|
56
|
+
error_message = f"{ui_found_message}\n\n{suggestion_message}"
|
57
|
+
raise ScaffoldValidateException(error_message)
|
58
|
+
except docker_errors.NotFound:
|
59
|
+
print(f"Skipping validating core service: {CORE_MOCK_UI_SERVICE_NAME}")
|
60
|
+
return
|
61
|
+
|
62
|
+
print(f"Validating core service: {CORE_MOCK_UI_SERVICE_NAME}")
|
63
|
+
container_missing_message = f"{bcolors.FAIL}Container '{mock_ui_container_name}' not found for service '{CORE_MOCK_UI_SERVICE_NAME}'{bcolors.ENDC}"
|
64
|
+
suggestion_message = f"{bcolors.BOLD}Stoobly UI is not running. Check if the container is up with 'docker ps -a | grep {mock_ui_container_name}'{bcolors.ENDC}"
|
65
|
+
mock_ui_missing_error_message = f"{container_missing_message}\n\n{suggestion_message}"
|
62
66
|
|
63
|
-
|
67
|
+
try:
|
68
|
+
mock_ui_container = self.docker_client.containers.get(mock_ui_container_name)
|
69
|
+
if not mock_ui_container or (mock_ui_container.status != 'running'):
|
70
|
+
raise ScaffoldValidateException(mock_ui_missing_error_message)
|
71
|
+
except docker_errors.NotFound:
|
72
|
+
raise ScaffoldValidateException(mock_ui_missing_error_message)
|
64
73
|
|
65
|
-
|
74
|
+
def validate_entrypoint_service(self):
|
75
|
+
print(f"Validating core service: {CORE_ENTRYPOINT_SERVICE_NAME}")
|
66
76
|
|
77
|
+
core_entrypoint_init_container_name = None
|
67
78
|
try:
|
68
79
|
core_entrypoint_init_container_name = self.managed_services_docker_compose.entrypoint_init_container_name
|
69
80
|
entrypoint_init_container = self.docker_client.containers.get(core_entrypoint_init_container_name)
|
70
81
|
except docker_errors.NotFound:
|
71
|
-
|
72
|
-
|
82
|
+
error_message = self._ValidateCommand__generate_container_not_found_error(core_entrypoint_init_container_name)
|
83
|
+
raise ScaffoldValidateException(error_message)
|
84
|
+
|
85
|
+
core_entrypoint_configure_container_name = None
|
73
86
|
try:
|
74
87
|
core_entrypoint_configure_container_name = self.managed_services_docker_compose.entrypoint_configure_container_name
|
75
88
|
entrypoint_configure_container = self.docker_client.containers.get(core_entrypoint_configure_container_name)
|
76
89
|
except docker_errors.NotFound:
|
77
|
-
|
90
|
+
error_message = self._ValidateCommand__generate_container_not_found_error(core_entrypoint_configure_container_name)
|
91
|
+
raise ScaffoldValidateException(error_message)
|
78
92
|
|
79
93
|
# NOTE: we should check the correct workflow mode is enabled one day
|
80
94
|
# That's not currently queryable
|
81
95
|
|
82
|
-
print(f"Done validating workflow: {self.workflow_name}, success
|
83
|
-
|
96
|
+
print(f"{bcolors.OKGREEN}✔ Done validating core services for workflow: {self.workflow_name}, success!\n{bcolors.ENDC}")
|
97
|
+
|
98
|
+
def validate_core_services(self):
|
99
|
+
self.validate_gateway_service()
|
100
|
+
self.validate_mock_ui_service()
|
101
|
+
self.validate_init_containers(self.managed_services_docker_compose.init_container_name, self.managed_services_docker_compose.configure_container_name)
|
102
|
+
self.validate_entrypoint_service()
|
103
|
+
|
104
|
+
def validate(self) -> bool:
|
105
|
+
print(f"Validating workflow: {self.workflow_name}\n")
|
106
|
+
print(f"Validating core services: {CORE_SERVICES}")
|
107
|
+
|
108
|
+
self.validate_core_services()
|
84
109
|
|
85
110
|
return True
|
86
111
|
|
@@ -518,6 +518,7 @@ def up(**kwargs):
|
|
518
518
|
|
519
519
|
__run_script(script, kwargs['dry_run'])
|
520
520
|
|
521
|
+
|
521
522
|
@workflow.command(
|
522
523
|
help="Validate a scaffold workflow"
|
523
524
|
)
|
@@ -536,7 +537,8 @@ def validate(**kwargs):
|
|
536
537
|
command = WorkflowValidateCommand(app, **config)
|
537
538
|
command.validate()
|
538
539
|
except ScaffoldValidateException as sve:
|
539
|
-
print(f"\nFatal
|
540
|
+
print(f"{bcolors.FAIL}\nFatal scaffold validation exception:{bcolors.ENDC}\n{sve}", file=sys.stderr)
|
541
|
+
print("\nSee the scaffold workflow troubleshooting guide at: https://docs.stoobly.com/guides/how-to-integrate-e2e-testing/how-to-run-a-workflow/troubleshooting", file=sys.stderr)
|
540
542
|
sys.exit(1)
|
541
543
|
|
542
544
|
try:
|
@@ -546,9 +548,12 @@ def validate(**kwargs):
|
|
546
548
|
command = ServiceWorkflowValidateCommand(app, **config)
|
547
549
|
command.validate()
|
548
550
|
except ScaffoldValidateException as sve:
|
549
|
-
print(f"\nFatal
|
551
|
+
print(f"{bcolors.FAIL}\nFatal scaffold validation exception:{bcolors.ENDC}\n{sve}", file=sys.stderr)
|
552
|
+
print("\nSee the scaffold workflow troubleshooting guide at: https://docs.stoobly.com/guides/how-to-integrate-e2e-testing/how-to-run-a-workflow/troubleshooting", file=sys.stderr)
|
550
553
|
sys.exit(1)
|
551
554
|
|
555
|
+
print(f"{bcolors.OKCYAN}✔ Done validating Stoobly scaffold and services, success!{bcolors.ENDC}")
|
556
|
+
|
552
557
|
@hostname.command(
|
553
558
|
help="Update the system hosts file for all scaffold service hostnames"
|
554
559
|
)
|
@@ -121,7 +121,7 @@ class TestScaffoldE2e():
|
|
121
121
|
# Validate docker-compose path exists
|
122
122
|
destination_path = Path(local_service_docker_compose.docker_compose_path)
|
123
123
|
assert destination_path.is_file()
|
124
|
-
# Add user defined Docker Compose file for the
|
124
|
+
# Add user defined Docker Compose file for the custom container service
|
125
125
|
shutil.copyfile(local_service_mock_docker_compose_path, destination_path)
|
126
126
|
|
127
127
|
# Record workflow doesn't have a public folder
|
@@ -137,7 +137,7 @@ class TestScaffoldE2e():
|
|
137
137
|
ScaffoldCliInvoker.cli_workflow_down(runner, app_dir_path, target_workflow_name)
|
138
138
|
shutil.rmtree(app_dir_path)
|
139
139
|
|
140
|
-
def
|
140
|
+
def test_core_services(self, app_dir_path, target_workflow_name):
|
141
141
|
app = App(app_dir_path, DOCKER_NAMESPACE)
|
142
142
|
config = {
|
143
143
|
'workflow_name': target_workflow_name,
|
@@ -260,7 +260,7 @@ class TestScaffoldE2e():
|
|
260
260
|
ScaffoldCliInvoker.cli_workflow_down(runner, app_dir_path, target_workflow_name)
|
261
261
|
shutil.rmtree(app_dir_path)
|
262
262
|
|
263
|
-
def
|
263
|
+
def test_no_core_services(self, app_dir_path, target_workflow_name):
|
264
264
|
app = App(app_dir_path, DOCKER_NAMESPACE)
|
265
265
|
config = {
|
266
266
|
'workflow_name': target_workflow_name,
|
@@ -11,7 +11,7 @@ class TestHostsFileManager():
|
|
11
11
|
yield HostsFileManager()
|
12
12
|
|
13
13
|
def test_get_hosts_file_path(self, hosts_file_manager):
|
14
|
-
hosts_file_path = hosts_file_manager.
|
14
|
+
hosts_file_path = hosts_file_manager.get_hosts_file_path()
|
15
15
|
|
16
16
|
# Test runners are all Linux distros for now
|
17
17
|
assert hosts_file_path == '/etc/hosts'
|
@@ -1 +1 @@
|
|
1
|
-
1.7.0
|
1
|
+
1.7.0
|
@@ -1,4 +1,4 @@
|
|
1
|
-
stoobly_agent/__init__.py,sha256=
|
1
|
+
stoobly_agent/__init__.py,sha256=lW3HNRv30I4AZM5G54ri20nFb4CRG36fCUHauTdkLFQ,44
|
2
2
|
stoobly_agent/app/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
3
3
|
stoobly_agent/app/api/__init__.py,sha256=ctkB8KR-eXO0SFhj602huHiyvQ3PslFWd8fkcufgrAI,1000
|
4
4
|
stoobly_agent/app/api/application_http_request_handler.py,sha256=Vvz53yB0bR7J-QqMAkLlhcZrA4P64ZEN7w8cMbgl6o0,5261
|
@@ -92,7 +92,7 @@ stoobly_agent/app/cli/scaffold/docker/workflow/dns_decorator.py,sha256=nlDPbyF1h
|
|
92
92
|
stoobly_agent/app/cli/scaffold/docker/workflow/mock_decorator.py,sha256=DcBnwA8YhE-VdrUiWf2xPcqirEMZEQm3AwssAg9CxLo,1226
|
93
93
|
stoobly_agent/app/cli/scaffold/docker/workflow/reverse_proxy_decorator.py,sha256=zD4FEtBMiPEtvEb798LOfFH8sJhTWQYpA9THLq5UMbg,1227
|
94
94
|
stoobly_agent/app/cli/scaffold/env.py,sha256=e-Ve4p3RUgzFx22B3SIYttvJ_yLuDtA27oDACZ8n-6E,1140
|
95
|
-
stoobly_agent/app/cli/scaffold/hosts_file_manager.py,sha256=
|
95
|
+
stoobly_agent/app/cli/scaffold/hosts_file_manager.py,sha256=zNX5wh6zXQ4J2BA0YYdD7_CPqDz02b_ghXsY3oTjjB4,4999
|
96
96
|
stoobly_agent/app/cli/scaffold/managed_services_docker_compose.py,sha256=-wLBXUi7DCWsfm5KzZzd_kdJKOTl1NT924XR7dyjbSY,574
|
97
97
|
stoobly_agent/app/cli/scaffold/service.py,sha256=L9K6QE0k5KSEC8_fSwtdwwTSO_DsIpqSPW-AG7Bg76o,501
|
98
98
|
stoobly_agent/app/cli/scaffold/service_command.py,sha256=9kIKiFC5Jo425VWYD4NDvUOdMP-pNyq2D5Ip1ZAPj3A,1054
|
@@ -101,9 +101,9 @@ stoobly_agent/app/cli/scaffold/service_create_command.py,sha256=1B6TK3JDAjouikCV
|
|
101
101
|
stoobly_agent/app/cli/scaffold/service_delete_command.py,sha256=_nBDQjm8eL62MQpzSCxgUHlW04ZXKG8MDlN1BXxlqww,986
|
102
102
|
stoobly_agent/app/cli/scaffold/service_docker_compose.py,sha256=OMUN1-ujQYIZXxDvS4XBf5C9wGalQULkwOiBBQPZbHY,820
|
103
103
|
stoobly_agent/app/cli/scaffold/service_workflow.py,sha256=sQ_Edy_wGHKMXpD0DmhnOWkGEKz7gSgEGNI8f7aXOdg,444
|
104
|
-
stoobly_agent/app/cli/scaffold/service_workflow_validate_command.py,sha256=
|
104
|
+
stoobly_agent/app/cli/scaffold/service_workflow_validate_command.py,sha256=RxPJollW_FQjlPcIbvqGRVH2adJYPcLNCsVMpW5wjxs,12740
|
105
105
|
stoobly_agent/app/cli/scaffold/templates/__init__.py,sha256=p-23uLU2QGksJASDD13soldc0vr_AOS3QdGlSnbs3bM,167
|
106
|
-
stoobly_agent/app/cli/scaffold/templates/app/.Dockerfile.context,sha256=
|
106
|
+
stoobly_agent/app/cli/scaffold/templates/app/.Dockerfile.context,sha256=AK8j4JWDmQxfsOX-Ysp-LNckPGhp-tiDOdPMdYItmso,158
|
107
107
|
stoobly_agent/app/cli/scaffold/templates/app/.Makefile,sha256=hree9QL8xl7v_Yzv40bcO4-ZDlyA4Qnm-VWU2dnMuXQ,8955
|
108
108
|
stoobly_agent/app/cli/scaffold/templates/app/.docker-compose.base.yml,sha256=6tFqXh3ine8vaD0FCL5TMoY5NjKx2wLUR8XpW3tJtew,245
|
109
109
|
stoobly_agent/app/cli/scaffold/templates/app/.docker-compose.networks.yml,sha256=I4PbJpQjFHb5IbAUWNvYM6okDEtmwtKFDQg-yog05WM,141
|
@@ -192,7 +192,7 @@ stoobly_agent/app/cli/scaffold/templates/workflow/test/bin/init,sha256=EaoFDyoJb
|
|
192
192
|
stoobly_agent/app/cli/scaffold/templates/workflow/test/fixtures.yml,sha256=CJlZ_kugygZpmyqIauBjNZxqk7XyLaa3yl3AWj8KV28,259
|
193
193
|
stoobly_agent/app/cli/scaffold/templates/workflow/test/lifecycle_hooks.py,sha256=U7mlzT_wBR3uhHSG6CAyt5tBUNAvdIrCw33gdB-F294,467
|
194
194
|
stoobly_agent/app/cli/scaffold/templates/workflow/test/public/.gitignore,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
195
|
-
stoobly_agent/app/cli/scaffold/validate_command.py,sha256=
|
195
|
+
stoobly_agent/app/cli/scaffold/validate_command.py,sha256=BEDcF5qPDbMjWe-SCAr5jxcrVMZlV3ciLa89TDDENmM,3787
|
196
196
|
stoobly_agent/app/cli/scaffold/validate_exceptions.py,sha256=Jtjl4OkbbSRWm0hy7Kf_50zcFh2J324HhNcnwJKH_Oc,54
|
197
197
|
stoobly_agent/app/cli/scaffold/workflow.py,sha256=KlbWT9CIo9EpZxKU1WVZtmodhxK7CpmLUHPNk4Mh6DA,1570
|
198
198
|
stoobly_agent/app/cli/scaffold/workflow_command.py,sha256=eI9I5LLgO0U3b46QhHusy-4BV2zUDVai6jErcluYCRI,3344
|
@@ -202,8 +202,8 @@ stoobly_agent/app/cli/scaffold/workflow_create_command.py,sha256=R3L1obUTidGngbI
|
|
202
202
|
stoobly_agent/app/cli/scaffold/workflow_env.py,sha256=x8V5pJmIiklD3f2q2-qq-CORf4YaXYq_r2JpR2MmSwk,416
|
203
203
|
stoobly_agent/app/cli/scaffold/workflow_log_command.py,sha256=Bke4lMOMxuDUFuAx9nlXHbKgYMO4KAg9ASHvjz4aVWc,1372
|
204
204
|
stoobly_agent/app/cli/scaffold/workflow_run_command.py,sha256=eF3aaK4OIZXYuSBEAeBnhAL7EZrS1G4mSYrJbEiXt2o,11082
|
205
|
-
stoobly_agent/app/cli/scaffold/workflow_validate_command.py,sha256=
|
206
|
-
stoobly_agent/app/cli/scaffold_cli.py,sha256=
|
205
|
+
stoobly_agent/app/cli/scaffold/workflow_validate_command.py,sha256=Uo_yo6rVR1ZR7xpvsQvlH48AyMBVLRupd4G-bRjzm_Q,5584
|
206
|
+
stoobly_agent/app/cli/scaffold_cli.py,sha256=FfI_v_Rpzoieou8W3l640zUrmGSAnBDSdp_h4ND8tGU,29135
|
207
207
|
stoobly_agent/app/cli/scenario_cli.py,sha256=3J1EiJOvunkfWrEkOsanw-XrKkOk78ij_GjBlE9p7CE,8229
|
208
208
|
stoobly_agent/app/cli/snapshot_cli.py,sha256=cpCjxFYBuVwLuq_b2lIUu-5zWqupRlrp4xWgDytirSM,10047
|
209
209
|
stoobly_agent/app/cli/trace_cli.py,sha256=K7E-vx3JUcqEDSWOdIOi_AieKNQz7dBfmRrVvKDkzFI,4605
|
@@ -677,8 +677,8 @@ stoobly_agent/test/app/cli/request/request_snapshot_test.py,sha256=0013aoiMZin-2
|
|
677
677
|
stoobly_agent/test/app/cli/request/request_test_test.py,sha256=-cJNXKjgryVVfVt-7IN5fIhBwe3NjFoPmeavDH8lAjU,5527
|
678
678
|
stoobly_agent/test/app/cli/scaffold/cli_invoker.py,sha256=_nGDLUsYxqkeqs5DdhvAeXy3IuotpgqKHXKVzu6GDF4,3700
|
679
679
|
stoobly_agent/test/app/cli/scaffold/cli_test.py,sha256=sMNvO845MIu5DVGa1HmwXQDmKDcwrfNTdEb3fK5886w,4557
|
680
|
-
stoobly_agent/test/app/cli/scaffold/e2e_test.py,sha256=
|
681
|
-
stoobly_agent/test/app/cli/scaffold/hosts_file_manager_test.py,sha256=
|
680
|
+
stoobly_agent/test/app/cli/scaffold/e2e_test.py,sha256=IGWT0EXrMtB8i8kdLFbN7O8NvLrJYTi-iQ_GRiUoA94,12978
|
681
|
+
stoobly_agent/test/app/cli/scaffold/hosts_file_manager_test.py,sha256=ztcPh1x0ZCW1FWA5YL4ulEVjfbW9TOPgk1bnSDPNmCw,2287
|
682
682
|
stoobly_agent/test/app/cli/scenario/scenario_create_test.py,sha256=fGqcjO1_1OvdpUMQfGRVkSyFe61u8WIcp_ndLFrf33A,3962
|
683
683
|
stoobly_agent/test/app/cli/scenario/scenario_replay_integration_test.py,sha256=NbGJzmvPsNLBR0ac65yt_cOTfpnsST1IG7i3F0euwAk,7031
|
684
684
|
stoobly_agent/test/app/cli/scenario/scenario_replay_test.py,sha256=XqR-GDrR8uUtxvukBuIFJ4GsOzBj9WWi4b0VKmfhFyE,6874
|
@@ -703,7 +703,7 @@ stoobly_agent/test/app/models/factories/resource/local_db/helpers/log_test.py,sh
|
|
703
703
|
stoobly_agent/test/app/models/factories/resource/local_db/helpers/tiebreak_scenario_request_test.py,sha256=a1SFLyEyRRLuADvAw6ckQQKORFXvyK1lyrbkaLWx8oU,3399
|
704
704
|
stoobly_agent/test/app/models/factories/resource/local_db/request_adapter_test.py,sha256=Pzq1cBPnP9oSWG-p0c-VoymoHxgp483QmNwmV1b78RA,8453
|
705
705
|
stoobly_agent/test/app/models/factories/resource/local_db/response_adapter_test.py,sha256=9P95EKH5rZGOrmRkRIDlQZqtiLJHk9735og18Ffwpfw,2204
|
706
|
-
stoobly_agent/test/app/models/schemas/.stoobly/db/VERSION,sha256=
|
706
|
+
stoobly_agent/test/app/models/schemas/.stoobly/db/VERSION,sha256=u3Mg2DHnoVGqkBw15zJsdS-i71Ak8wdoxCMZuL7Rce0,6
|
707
707
|
stoobly_agent/test/app/models/schemas/.stoobly/db/stoobly_agent.sqlite3,sha256=ch8gNx6zIelLKQx65gwFx_LRNqUD3EC5xcHZ0ukIQiU,188416
|
708
708
|
stoobly_agent/test/app/models/schemas/.stoobly/settings.yml,sha256=vLwMjweKOdod6tSLtIlyBefPQuNXq9wio4kBaODKtAU,726
|
709
709
|
stoobly_agent/test/app/models/schemas/.stoobly/tmp/options.json,sha256=OTRzarwus48CTrItedXCrgQttJHSEZonEYc7R_knvYg,2212
|
@@ -744,8 +744,8 @@ stoobly_agent/test/mock_data/scaffold/docker-compose-local-service.yml,sha256=1W
|
|
744
744
|
stoobly_agent/test/mock_data/scaffold/index.html,sha256=qJwuYajKZ4ihWZrJQ3BNObV5kf1VGnnm_vqlPJzdqLE,258
|
745
745
|
stoobly_agent/test/mock_data/uspto.yaml,sha256=6U5se7C3o-86J4m9xpOk9Npias399f5CbfWzR87WKwE,7835
|
746
746
|
stoobly_agent/test/test_helper.py,sha256=m_oAI7tmRYCNZdKfNqISWhMv3e44tjeYViQ3nTUfnos,1007
|
747
|
-
stoobly_agent-1.7.
|
748
|
-
stoobly_agent-1.7.
|
749
|
-
stoobly_agent-1.7.
|
750
|
-
stoobly_agent-1.7.
|
751
|
-
stoobly_agent-1.7.
|
747
|
+
stoobly_agent-1.7.1.dist-info/LICENSE,sha256=o93sj12cdoEOsTCjPaPFsw3Xq0SXs3pPcY-9reE2sEw,548
|
748
|
+
stoobly_agent-1.7.1.dist-info/METADATA,sha256=ljJB5pxOKkbXohMwMwaOTX5_5rRkxDJYtX2-tzM3kgY,3087
|
749
|
+
stoobly_agent-1.7.1.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
|
750
|
+
stoobly_agent-1.7.1.dist-info/entry_points.txt,sha256=aq5wix5oC8MDQtmyPGU0xaFrsjJg7WH28NmXh2sc3Z8,56
|
751
|
+
stoobly_agent-1.7.1.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|