stoobly-agent 1.0.7__py3-none-any.whl → 1.0.9__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/constants.py +9 -1
- stoobly_agent/app/cli/scaffold/docker/constants.py +6 -0
- stoobly_agent/app/cli/scaffold/docker/workflow/builder.py +0 -2
- stoobly_agent/app/cli/scaffold/docker/workflow/mock_decorator.py +1 -1
- stoobly_agent/app/cli/scaffold/docker/workflow/reverse_proxy_decorator.py +0 -1
- stoobly_agent/app/cli/scaffold/managed_services_docker_compose.py +9 -0
- stoobly_agent/app/cli/scaffold/service_command.py +3 -1
- stoobly_agent/app/cli/scaffold/service_config.py +2 -1
- stoobly_agent/app/cli/scaffold/service_docker_compose.py +15 -0
- stoobly_agent/app/cli/scaffold/service_workflow_validate_command.py +234 -0
- stoobly_agent/app/cli/scaffold/templates/app/.Makefile +2 -2
- stoobly_agent/app/cli/scaffold/templates/app/entrypoint/mock/docker-compose.yml +14 -1
- stoobly_agent/app/cli/scaffold/templates/app/entrypoint/record/docker-compose.yml +14 -1
- stoobly_agent/app/cli/scaffold/templates/app/entrypoint/test/docker-compose.yml +14 -1
- stoobly_agent/app/cli/scaffold/templates/app/stoobly-ui/exec/bin/.run +2 -0
- stoobly_agent/app/cli/scaffold/templates/constants.py +3 -3
- stoobly_agent/app/cli/scaffold/validate_command.py +59 -0
- stoobly_agent/app/cli/scaffold/validate_exceptions.py +5 -0
- stoobly_agent/app/cli/scaffold/workflow.py +22 -1
- stoobly_agent/app/cli/scaffold/workflow_run_command.py +17 -3
- stoobly_agent/app/cli/scaffold/workflow_validate_command.py +94 -0
- stoobly_agent/app/cli/scaffold_cli.py +48 -5
- stoobly_agent/config/data_dir.py +11 -2
- stoobly_agent/test/app/cli/scaffold/e2e_test.py +428 -0
- stoobly_agent/test/app/models/schemas/.stoobly/db/VERSION +1 -1
- stoobly_agent/test/mock_data/scaffold/docker-compose-assets-service.yml +18 -0
- stoobly_agent/test/mock_data/scaffold/docker-compose-local-service.yml +16 -0
- stoobly_agent/test/mock_data/scaffold/index.html +12 -0
- {stoobly_agent-1.0.7.dist-info → stoobly_agent-1.0.9.dist-info}/METADATA +2 -1
- {stoobly_agent-1.0.7.dist-info → stoobly_agent-1.0.9.dist-info}/RECORD +34 -24
- {stoobly_agent-1.0.7.dist-info → stoobly_agent-1.0.9.dist-info}/LICENSE +0 -0
- {stoobly_agent-1.0.7.dist-info → stoobly_agent-1.0.9.dist-info}/WHEEL +0 -0
- {stoobly_agent-1.0.7.dist-info → stoobly_agent-1.0.9.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,94 @@
|
|
1
|
+
import pdb
|
2
|
+
|
3
|
+
from docker import errors as docker_errors
|
4
|
+
|
5
|
+
from stoobly_agent.app.cli.scaffold.constants import WORKFLOW_TEST_TYPE
|
6
|
+
from stoobly_agent.app.cli.scaffold.managed_services_docker_compose import (
|
7
|
+
ManagedServicesDockerCompose,
|
8
|
+
)
|
9
|
+
from stoobly_agent.app.cli.scaffold.templates.constants import (
|
10
|
+
CORE_ENTRYPOINT_SERVICE_NAME,
|
11
|
+
CORE_GATEWAY_SERVICE_NAME,
|
12
|
+
CORE_MOCK_UI_SERVICE_NAME,
|
13
|
+
CORE_SERVICES,
|
14
|
+
)
|
15
|
+
from stoobly_agent.app.cli.scaffold.validate_command import ValidateCommand
|
16
|
+
from stoobly_agent.app.cli.scaffold.validate_exceptions import ScaffoldValidateException
|
17
|
+
from stoobly_agent.app.cli.scaffold.workflow_command import WorkflowCommand
|
18
|
+
|
19
|
+
from .app import App
|
20
|
+
|
21
|
+
|
22
|
+
class WorkflowValidateCommand(WorkflowCommand, ValidateCommand):
|
23
|
+
def __init__(self, app: App, **kwargs):
|
24
|
+
WorkflowCommand.__init__(self, app, **kwargs)
|
25
|
+
ValidateCommand.__init__(self)
|
26
|
+
self.managed_services_docker_compose = ManagedServicesDockerCompose(target_workflow_name=self.workflow_name)
|
27
|
+
|
28
|
+
def validate_core_components(self):
|
29
|
+
print(f"Validating core component: {CORE_GATEWAY_SERVICE_NAME}")
|
30
|
+
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
|
+
|
35
|
+
print(f"Validating core component: {CORE_MOCK_UI_SERVICE_NAME}")
|
36
|
+
mock_ui_container_name = self.managed_services_docker_compose.mock_ui_container_name
|
37
|
+
mock_ui_container = self.docker_client.containers.get(mock_ui_container_name)
|
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}'")
|
40
|
+
|
41
|
+
def validate_no_core_components(self):
|
42
|
+
try:
|
43
|
+
core_gateway_container = self.docker_client.containers.get(self.managed_services_docker_compose.gateway_container_name)
|
44
|
+
if core_gateway_container:
|
45
|
+
raise ScaffoldValidateException(f"Gateway container is running when it shouldn't: {core_gateway_container.name}")
|
46
|
+
except docker_errors.NotFound:
|
47
|
+
pass
|
48
|
+
|
49
|
+
try:
|
50
|
+
core_mock_ui_container_name = self.docker_client.containers.get(self.managed_services_docker_compose.mock_ui_container_name)
|
51
|
+
if core_mock_ui_container_name:
|
52
|
+
raise ScaffoldValidateException(f"Stoobly UI container is running when it shouldn't: {core_mock_ui_container_name.name}")
|
53
|
+
except docker_errors.NotFound:
|
54
|
+
pass
|
55
|
+
|
56
|
+
print(f"Skipping validating core component: {CORE_GATEWAY_SERVICE_NAME}")
|
57
|
+
print(f"Skipping validating core component: {CORE_MOCK_UI_SERVICE_NAME}")
|
58
|
+
|
59
|
+
|
60
|
+
def validate(self) -> bool:
|
61
|
+
print(f"Validating workflow: {self.workflow_name}")
|
62
|
+
print(f"Validating core components: {CORE_SERVICES}")
|
63
|
+
|
64
|
+
if self.workflow_name == WORKFLOW_TEST_TYPE:
|
65
|
+
# Don't validate the gateway and mock_ui core components in the "test" workflow
|
66
|
+
self.validate_no_core_components()
|
67
|
+
else:
|
68
|
+
self.validate_core_components()
|
69
|
+
|
70
|
+
self.validate_init_containers(self.managed_services_docker_compose.init_container_name, self.managed_services_docker_compose.configure_container_name)
|
71
|
+
|
72
|
+
print(f"Validating core component: {CORE_ENTRYPOINT_SERVICE_NAME}")
|
73
|
+
|
74
|
+
try:
|
75
|
+
core_entrypoint_init_container_name = self.managed_services_docker_compose.entrypoint_init_container_name
|
76
|
+
entrypoint_init_container = self.docker_client.containers.get(core_entrypoint_init_container_name)
|
77
|
+
except docker_errors.NotFound:
|
78
|
+
raise ScaffoldValidateException(f"Container not found: {core_entrypoint_init_container_name}")
|
79
|
+
|
80
|
+
try:
|
81
|
+
core_entrypoint_configure_container_name = self.managed_services_docker_compose.entrypoint_configure_container_name
|
82
|
+
entrypoint_configure_container = self.docker_client.containers.get(core_entrypoint_configure_container_name)
|
83
|
+
except docker_errors.NotFound:
|
84
|
+
raise ScaffoldValidateException(f"Container not found: {core_entrypoint_configure_container_name}")
|
85
|
+
|
86
|
+
# NOTE: we should check the correct workflow mode is enabled one day
|
87
|
+
# That's not currently queryable
|
88
|
+
|
89
|
+
print(f"Done validating workflow: {self.workflow_name}, success!")
|
90
|
+
print()
|
91
|
+
|
92
|
+
return True
|
93
|
+
|
94
|
+
|
@@ -9,6 +9,7 @@ from stoobly_agent.app.cli.helpers.certificate_authority import CertificateAutho
|
|
9
9
|
from stoobly_agent.app.cli.helpers.shell import exec_stream
|
10
10
|
from stoobly_agent.app.cli.scaffold.app import App
|
11
11
|
from stoobly_agent.app.cli.scaffold.app_create_command import AppCreateCommand
|
12
|
+
from stoobly_agent.app.cli.scaffold.constants import DOCKER_NAMESPACE, WORKFLOW_MOCK_TYPE, WORKFLOW_RECORD_TYPE, WORKFLOW_TEST_TYPE
|
12
13
|
from stoobly_agent.app.cli.scaffold.constants import (
|
13
14
|
DOCKER_NAMESPACE, WORKFLOW_CUSTOM_FILTER, WORKFLOW_MOCK_TYPE, WORKFLOW_RECORD_TYPE, WORKFLOW_TEST_TYPE
|
14
15
|
)
|
@@ -17,12 +18,15 @@ from stoobly_agent.app.cli.scaffold.docker.workflow.decorators_factory import ge
|
|
17
18
|
from stoobly_agent.app.cli.scaffold.service import Service
|
18
19
|
from stoobly_agent.app.cli.scaffold.service_config import ServiceConfig
|
19
20
|
from stoobly_agent.app.cli.scaffold.service_create_command import ServiceCreateCommand
|
21
|
+
from stoobly_agent.app.cli.scaffold.service_workflow_validate_command import ServiceWorkflowValidateCommand
|
20
22
|
from stoobly_agent.app.cli.scaffold.templates.constants import CORE_SERVICES
|
23
|
+
from stoobly_agent.app.cli.scaffold.validate_exceptions import ScaffoldValidateException
|
21
24
|
from stoobly_agent.app.cli.scaffold.workflow import Workflow
|
22
25
|
from stoobly_agent.app.cli.scaffold.workflow_create_command import WorkflowCreateCommand
|
23
26
|
from stoobly_agent.app.cli.scaffold.workflow_copy_command import WorkflowCopyCommand
|
24
27
|
from stoobly_agent.app.cli.scaffold.workflow_log_command import WorkflowLogCommand
|
25
28
|
from stoobly_agent.app.cli.scaffold.workflow_run_command import WorkflowRunCommand
|
29
|
+
from stoobly_agent.app.cli.scaffold.workflow_validate_command import WorkflowValidateCommand
|
26
30
|
from stoobly_agent.config.constants import env_vars
|
27
31
|
from stoobly_agent.config.data_dir import DataDir
|
28
32
|
from stoobly_agent.lib.logger import bcolors, DEBUG, ERROR, INFO, Logger, WARNING
|
@@ -312,18 +316,23 @@ def logs(**kwargs):
|
|
312
316
|
@click.option('--ca-certs-dir-path', default=DataDir.instance().mitmproxy_conf_dir_path, help='Path to ca certs directory used to sign SSL certs. Defaults to ~/.mitmproxy')
|
313
317
|
@click.option('--certs-dir-path', help='Path to certs directory. Defaults to the certs dir of the context.')
|
314
318
|
@click.option('--context-dir-path', default=DataDir.instance().context_dir_path, help='Path to Stoobly data directory.')
|
319
|
+
@click.option('--detached', is_flag=True, help='If set, will not run the highest priority service in the foreground.')
|
315
320
|
@click.option('--filter', multiple=True, type=click.Choice([WORKFLOW_CUSTOM_FILTER]), help='Select which service groups to run. Defaults to all.')
|
316
321
|
@click.option('--dry-run', default=False, is_flag=True, help='If set, prints commands.')
|
317
322
|
@click.option('--extra-compose-path', help='Path to extra compose configuration files.')
|
318
323
|
@click.option('--log-level', default=INFO, type=click.Choice([DEBUG, INFO, WARNING, ERROR]), help='''
|
319
324
|
Log levels can be "debug", "info", "warning", or "error"
|
320
325
|
''')
|
321
|
-
@click.option('--
|
326
|
+
@click.option('--namespace', help='Workflow namespace.')
|
327
|
+
@click.option('--network', help='Workflow network namespace.')
|
322
328
|
@click.option('--service', multiple=True, help='Select which services to run. Defaults to all.')
|
323
329
|
@click.argument('workflow_name')
|
324
330
|
def run(**kwargs):
|
325
331
|
cwd = os.getcwd()
|
326
332
|
|
333
|
+
# Create the certs_dir_path if it doesn't exist
|
334
|
+
DataDir.instance().certs_dir_path
|
335
|
+
|
327
336
|
if not os.getenv(env_vars.LOG_LEVEL):
|
328
337
|
os.environ[env_vars.LOG_LEVEL] = kwargs['log_level']
|
329
338
|
|
@@ -365,10 +374,13 @@ def run(**kwargs):
|
|
365
374
|
print(create_network_command)
|
366
375
|
|
367
376
|
commands = sorted(commands, key=lambda command: command.service_config.priority)
|
368
|
-
for command in commands:
|
377
|
+
for index, command in enumerate(commands):
|
369
378
|
__print_header(f"SERVICE {command.service_name}")
|
370
379
|
|
371
|
-
|
380
|
+
# By default, the entrypoint service should be last
|
381
|
+
# However, this can change if the user has configured a service's priority to be higher
|
382
|
+
attached = not kwargs['detached'] and index == len(commands) - 1
|
383
|
+
exec_command = command.up(attached=attached, namespace=kwargs['namespace'])
|
372
384
|
if not exec_command:
|
373
385
|
continue
|
374
386
|
|
@@ -376,7 +388,37 @@ def run(**kwargs):
|
|
376
388
|
exec_stream(exec_command)
|
377
389
|
else:
|
378
390
|
print(exec_command)
|
379
|
-
|
391
|
+
|
392
|
+
@workflow.command(
|
393
|
+
help="Validate a scaffold workflow"
|
394
|
+
)
|
395
|
+
@click.option('--app-dir-path', default=os.getcwd(), help='Path to validate the app scaffold.')
|
396
|
+
@click.argument('workflow_name')
|
397
|
+
def validate(**kwargs):
|
398
|
+
app = App(kwargs['app_dir_path'], DOCKER_NAMESPACE)
|
399
|
+
workflow = Workflow(kwargs['workflow_name'], app)
|
400
|
+
|
401
|
+
config = { **kwargs }
|
402
|
+
config['service_name'] = 'build'
|
403
|
+
|
404
|
+
try:
|
405
|
+
command = WorkflowValidateCommand(app, **config)
|
406
|
+
command.validate()
|
407
|
+
except ScaffoldValidateException as sve:
|
408
|
+
print(f"\nFatal Scaffold Validation Exception: {sve}", file=sys.stderr)
|
409
|
+
sys.exit(1)
|
410
|
+
|
411
|
+
try:
|
412
|
+
for service in workflow.services_ran:
|
413
|
+
if service not in CORE_SERVICES:
|
414
|
+
config['service_name'] = service
|
415
|
+
command = ServiceWorkflowValidateCommand(app, **config)
|
416
|
+
command.validate()
|
417
|
+
except ScaffoldValidateException as sve:
|
418
|
+
print(f"\nFatal Scaffold Validation Exception: {sve}", file=sys.stderr)
|
419
|
+
sys.exit(1)
|
420
|
+
|
421
|
+
|
380
422
|
scaffold.add_command(app)
|
381
423
|
scaffold.add_command(service)
|
382
424
|
scaffold.add_command(workflow)
|
@@ -431,4 +473,5 @@ def __workflow_build(app, **kwargs):
|
|
431
473
|
headless=kwargs['headless'],
|
432
474
|
template=kwargs['template'],
|
433
475
|
workflow_decorators=workflow_decorators
|
434
|
-
)
|
476
|
+
)
|
477
|
+
|
stoobly_agent/config/data_dir.py
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
import pdb
|
1
2
|
import os
|
2
3
|
import shutil
|
3
4
|
|
@@ -16,6 +17,8 @@ class DataDir:
|
|
16
17
|
if DataDir._instances.get(path):
|
17
18
|
raise RuntimeError('Call instance() instead')
|
18
19
|
else:
|
20
|
+
self.__path = path
|
21
|
+
|
19
22
|
if path:
|
20
23
|
self.__data_dir_path = os.path.join(path, DATA_DIR_NAME)
|
21
24
|
else:
|
@@ -35,6 +38,9 @@ class DataDir:
|
|
35
38
|
if not os.path.exists(self.__data_dir_path):
|
36
39
|
self.create(os.path.dirname(self.__data_dir_path))
|
37
40
|
|
41
|
+
def __repr__(self) -> str:
|
42
|
+
return self.path
|
43
|
+
|
38
44
|
@classmethod
|
39
45
|
def instance(cls, path: str = None):
|
40
46
|
if not cls._instances:
|
@@ -58,7 +64,7 @@ class DataDir:
|
|
58
64
|
|
59
65
|
@property
|
60
66
|
def path(self):
|
61
|
-
if os.environ.get(ENV) == 'test':
|
67
|
+
if not self.__path and os.environ.get(ENV) == 'test':
|
62
68
|
test_path = os.path.join(self.__data_dir_path, 'tmp', DATA_DIR_NAME)
|
63
69
|
|
64
70
|
if not os.path.exists(test_path):
|
@@ -212,12 +218,15 @@ class DataDir:
|
|
212
218
|
if not os.path.exists(self.__data_dir_path):
|
213
219
|
os.mkdir(self.__data_dir_path)
|
214
220
|
|
221
|
+
# Create the certs_dir_path if it doesn't exist
|
222
|
+
self.certs_dir_path
|
215
223
|
# Create tmp folder
|
216
|
-
os.
|
224
|
+
os.makedirs(os.path.join(self.__data_dir_path, 'tmp'), exist_ok=True)
|
217
225
|
|
218
226
|
with open(os.path.join(self.__data_dir_path, '.gitignore'), 'w') as fp:
|
219
227
|
fp.write(
|
220
228
|
"\n".join([
|
229
|
+
'certs',
|
221
230
|
'db',
|
222
231
|
'settings.yml',
|
223
232
|
os.path.join('snapshots', 'log'),
|
@@ -0,0 +1,428 @@
|
|
1
|
+
from pathlib import Path
|
2
|
+
import pathlib
|
3
|
+
import pdb
|
4
|
+
import shutil
|
5
|
+
|
6
|
+
from click.testing import CliRunner
|
7
|
+
import docker
|
8
|
+
import pytest
|
9
|
+
|
10
|
+
from stoobly_agent.app.cli import scaffold
|
11
|
+
from stoobly_agent.app.cli.scaffold.app import App
|
12
|
+
from stoobly_agent.app.cli.scaffold.constants import (
|
13
|
+
WORKFLOW_RECORD_TYPE,
|
14
|
+
WORKFLOW_TEST_TYPE,
|
15
|
+
)
|
16
|
+
from stoobly_agent.app.cli.scaffold.constants import DOCKER_NAMESPACE
|
17
|
+
from stoobly_agent.app.cli.scaffold.managed_services_docker_compose import (
|
18
|
+
ManagedServicesDockerCompose,
|
19
|
+
)
|
20
|
+
from stoobly_agent.app.cli.scaffold.service_docker_compose import ServiceDockerCompose
|
21
|
+
from stoobly_agent.app.cli.scaffold.service_workflow_validate_command import (
|
22
|
+
ServiceWorkflowValidateCommand,
|
23
|
+
)
|
24
|
+
from stoobly_agent.app.cli.scaffold.workflow_validate_command import (
|
25
|
+
WorkflowValidateCommand,
|
26
|
+
)
|
27
|
+
from stoobly_agent.config.data_dir import DATA_DIR_NAME, DataDir
|
28
|
+
from stoobly_agent.test.test_helper import reset
|
29
|
+
|
30
|
+
|
31
|
+
@pytest.mark.e2e
|
32
|
+
class TestScaffoldE2e():
|
33
|
+
|
34
|
+
@pytest.fixture(scope='module', autouse=True)
|
35
|
+
def settings(self):
|
36
|
+
return reset()
|
37
|
+
|
38
|
+
@pytest.fixture(scope='module')
|
39
|
+
def runner(self):
|
40
|
+
yield CliRunner()
|
41
|
+
|
42
|
+
@pytest.fixture(scope='class')
|
43
|
+
def docker_client(self):
|
44
|
+
yield docker.from_env()
|
45
|
+
|
46
|
+
@pytest.fixture(scope='class')
|
47
|
+
def app_name(self):
|
48
|
+
yield "0.0.1"
|
49
|
+
|
50
|
+
@pytest.fixture(scope='class', autouse=True)
|
51
|
+
def temp_dir(self):
|
52
|
+
data_dir_path = DataDir.instance().path
|
53
|
+
tmp_path = data_dir_path[:data_dir_path.rfind('/')]
|
54
|
+
|
55
|
+
yield tmp_path
|
56
|
+
|
57
|
+
@pytest.fixture(scope='class', autouse=True)
|
58
|
+
def app_dir_path(self, temp_dir, app_name):
|
59
|
+
yield temp_dir
|
60
|
+
|
61
|
+
@pytest.fixture(scope='class')
|
62
|
+
def mock_data_directory_path(self):
|
63
|
+
yield Path(__file__).parent.parent.parent.parent / 'mock_data'
|
64
|
+
|
65
|
+
@pytest.fixture(scope='class')
|
66
|
+
def local_service_mock_docker_compose_path(self, mock_data_directory_path):
|
67
|
+
path = mock_data_directory_path / "scaffold" / "docker-compose-local-service.yml"
|
68
|
+
yield path
|
69
|
+
|
70
|
+
@pytest.fixture(scope='class')
|
71
|
+
def hostname(self):
|
72
|
+
yield "http.badssl.com"
|
73
|
+
|
74
|
+
@pytest.fixture(scope='class')
|
75
|
+
def https_service_hostname(self):
|
76
|
+
yield "example.com"
|
77
|
+
|
78
|
+
@pytest.fixture(scope='class')
|
79
|
+
def external_service_name(self):
|
80
|
+
yield "external-service"
|
81
|
+
|
82
|
+
@pytest.fixture(scope='class')
|
83
|
+
def external_https_service_name(self):
|
84
|
+
yield "external-https-service"
|
85
|
+
|
86
|
+
@pytest.fixture(scope='class')
|
87
|
+
def local_hostname(self):
|
88
|
+
yield "my-httpbin.com"
|
89
|
+
|
90
|
+
@pytest.fixture(scope='class')
|
91
|
+
def local_service_name(self):
|
92
|
+
yield "my-httpbin"
|
93
|
+
|
94
|
+
|
95
|
+
class TestRecordWorkflow():
|
96
|
+
@pytest.fixture(scope='class', autouse=True)
|
97
|
+
def target_workflow_name(self):
|
98
|
+
yield WORKFLOW_RECORD_TYPE
|
99
|
+
|
100
|
+
@pytest.fixture(scope='class')
|
101
|
+
def managed_services_docker_compose(self, target_workflow_name):
|
102
|
+
yield ManagedServicesDockerCompose(target_workflow_name=target_workflow_name)
|
103
|
+
|
104
|
+
@pytest.fixture(scope='class')
|
105
|
+
def external_service_docker_compose(self, app_dir_path, target_workflow_name, external_service_name, hostname):
|
106
|
+
yield ServiceDockerCompose(app_dir_path=app_dir_path, target_workflow_name=target_workflow_name, service_name=external_service_name, hostname=hostname)
|
107
|
+
|
108
|
+
@pytest.fixture(scope='class')
|
109
|
+
def external_https_service_docker_compose(self, app_dir_path, target_workflow_name, external_https_service_name, https_service_hostname):
|
110
|
+
yield ServiceDockerCompose(app_dir_path=app_dir_path, target_workflow_name=target_workflow_name, service_name=external_https_service_name, hostname=https_service_hostname)
|
111
|
+
|
112
|
+
@pytest.fixture(scope='class')
|
113
|
+
def local_service_docker_compose(self, app_dir_path, target_workflow_name, local_service_name, local_hostname):
|
114
|
+
yield ServiceDockerCompose(app_dir_path=app_dir_path, target_workflow_name=target_workflow_name, service_name=local_service_name, hostname=local_hostname)
|
115
|
+
|
116
|
+
@pytest.fixture(scope='class', autouse=True)
|
117
|
+
def setup_docker_composes(self, managed_services_docker_compose, external_service_docker_compose, local_service_docker_compose):
|
118
|
+
self.managed_services_docker_compose = managed_services_docker_compose
|
119
|
+
self.external_service_docker_compose = external_service_docker_compose
|
120
|
+
self.local_service_docker_compose = local_service_docker_compose
|
121
|
+
|
122
|
+
@pytest.fixture(scope="class", autouse=True)
|
123
|
+
def create_scaffold_setup(self, runner, app_dir_path, app_name, target_workflow_name, external_service_docker_compose, external_https_service_docker_compose, local_service_docker_compose, local_service_mock_docker_compose_path):
|
124
|
+
TestScaffoldE2e.cli_app_create(runner, app_dir_path, app_name)
|
125
|
+
|
126
|
+
# Create external user defined service
|
127
|
+
TestScaffoldE2e.cli_service_create(runner, app_dir_path, external_service_docker_compose.hostname, external_service_docker_compose.service_name, False)
|
128
|
+
TestScaffoldE2e.cli_service_create(runner, app_dir_path, external_https_service_docker_compose.hostname, external_https_service_docker_compose.service_name, True)
|
129
|
+
|
130
|
+
# Create local user defined service
|
131
|
+
TestScaffoldE2e.cli_service_create(runner, app_dir_path, local_service_docker_compose.hostname, local_service_docker_compose.service_name, False)
|
132
|
+
|
133
|
+
# Validate docker-compose path exists
|
134
|
+
destination_path = Path(local_service_docker_compose.docker_compose_path)
|
135
|
+
assert destination_path.is_file()
|
136
|
+
# Add user defined Docker Compose file for the local service
|
137
|
+
shutil.copyfile(local_service_mock_docker_compose_path, destination_path)
|
138
|
+
|
139
|
+
# Record workflow doesn't have a fixtures folder
|
140
|
+
|
141
|
+
# Generate certs
|
142
|
+
TestScaffoldE2e.cli_app_mkcert(runner, app_dir_path)
|
143
|
+
|
144
|
+
TestScaffoldE2e.cli_workflow_run(runner, app_dir_path, target_workflow_name)
|
145
|
+
|
146
|
+
@pytest.fixture(scope="class", autouse=True)
|
147
|
+
def cleanup_after_all(self, runner, app_dir_path, target_workflow_name):
|
148
|
+
yield
|
149
|
+
TestScaffoldE2e.cli_workflow_stop(runner, app_dir_path, target_workflow_name)
|
150
|
+
|
151
|
+
def test_core_components(self, app_dir_path, target_workflow_name):
|
152
|
+
app = App(app_dir_path, DOCKER_NAMESPACE)
|
153
|
+
config = {
|
154
|
+
'workflow_name': target_workflow_name,
|
155
|
+
'service_name': 'build'
|
156
|
+
}
|
157
|
+
|
158
|
+
command = WorkflowValidateCommand(app, **config)
|
159
|
+
command.validate()
|
160
|
+
|
161
|
+
def test_external_service(self, external_service_docker_compose: ServiceDockerCompose, app_dir_path, target_workflow_name):
|
162
|
+
app = App(app_dir_path, DOCKER_NAMESPACE)
|
163
|
+
config = {
|
164
|
+
'workflow_name': target_workflow_name,
|
165
|
+
'service_name': external_service_docker_compose.service_name
|
166
|
+
}
|
167
|
+
|
168
|
+
command = ServiceWorkflowValidateCommand(app, **config)
|
169
|
+
command.validate()
|
170
|
+
|
171
|
+
def test_local_service(self, app_dir_path, target_workflow_name, local_service_docker_compose: ServiceDockerCompose):
|
172
|
+
app = App(app_dir_path, DOCKER_NAMESPACE)
|
173
|
+
config = {
|
174
|
+
'workflow_name': target_workflow_name,
|
175
|
+
'service_name': local_service_docker_compose.service_name
|
176
|
+
}
|
177
|
+
|
178
|
+
command = ServiceWorkflowValidateCommand(app, **config)
|
179
|
+
command.validate()
|
180
|
+
|
181
|
+
|
182
|
+
class TestTestWorkflow():
|
183
|
+
@pytest.fixture(scope='class')
|
184
|
+
def assets_service_mock_docker_compose_path(self, mock_data_directory_path):
|
185
|
+
path = mock_data_directory_path / "scaffold" / "docker-compose-assets-service.yml"
|
186
|
+
yield path
|
187
|
+
|
188
|
+
@pytest.fixture(scope='class')
|
189
|
+
def assets_service_assets_path(self, mock_data_directory_path):
|
190
|
+
path = mock_data_directory_path / "scaffold" / "index.html"
|
191
|
+
yield path
|
192
|
+
|
193
|
+
@pytest.fixture(scope='class', autouse=True)
|
194
|
+
def target_workflow_name(self):
|
195
|
+
yield WORKFLOW_TEST_TYPE
|
196
|
+
|
197
|
+
@pytest.fixture(scope='class')
|
198
|
+
def assets_service_name(self):
|
199
|
+
yield "assets"
|
200
|
+
|
201
|
+
@pytest.fixture(scope='class')
|
202
|
+
def assets_hostname(self):
|
203
|
+
yield "assets"
|
204
|
+
|
205
|
+
@pytest.fixture(scope='class')
|
206
|
+
def managed_services_docker_compose(self, target_workflow_name):
|
207
|
+
yield ManagedServicesDockerCompose(target_workflow_name=target_workflow_name)
|
208
|
+
|
209
|
+
@pytest.fixture(scope='class')
|
210
|
+
def external_service_docker_compose(self, app_dir_path, target_workflow_name, external_service_name, hostname):
|
211
|
+
yield ServiceDockerCompose(app_dir_path=app_dir_path, target_workflow_name=target_workflow_name, service_name=external_service_name, hostname=hostname)
|
212
|
+
|
213
|
+
@pytest.fixture(scope='class')
|
214
|
+
def local_service_docker_compose(self, app_dir_path, target_workflow_name, local_service_name, local_hostname):
|
215
|
+
yield ServiceDockerCompose(app_dir_path=app_dir_path, target_workflow_name=target_workflow_name, service_name=local_service_name, hostname=local_hostname)
|
216
|
+
|
217
|
+
@pytest.fixture(scope='class')
|
218
|
+
def assets_service_docker_compose(self, app_dir_path, target_workflow_name, assets_service_name, assets_hostname):
|
219
|
+
yield ServiceDockerCompose(app_dir_path=app_dir_path, target_workflow_name=target_workflow_name, service_name=assets_service_name, hostname=assets_hostname)
|
220
|
+
|
221
|
+
@pytest.fixture(scope='class', autouse=True)
|
222
|
+
def setup_docker_composes(self, managed_services_docker_compose, external_service_docker_compose, local_service_docker_compose, assets_service_docker_compose):
|
223
|
+
self.managed_services_docker_compose = managed_services_docker_compose
|
224
|
+
self.external_service_docker_compose = external_service_docker_compose
|
225
|
+
self.local_service_docker_compose = local_service_docker_compose
|
226
|
+
self.assets_service_docker_compose = assets_service_docker_compose
|
227
|
+
|
228
|
+
@pytest.fixture(scope="class", autouse=True)
|
229
|
+
def create_scaffold_setup(self, runner, app_name, app_dir_path, target_workflow_name, external_service_docker_compose, local_service_docker_compose, assets_service_docker_compose, mock_data_directory_path, assets_service_mock_docker_compose_path):
|
230
|
+
|
231
|
+
TestScaffoldE2e.cli_app_create(runner, app_dir_path, app_name)
|
232
|
+
|
233
|
+
# Create external user defined service
|
234
|
+
TestScaffoldE2e.cli_service_create(runner, app_dir_path, external_service_docker_compose.hostname, external_service_docker_compose.service_name, False)
|
235
|
+
# Create local user defined services
|
236
|
+
TestScaffoldE2e.cli_service_create(runner, app_dir_path, local_service_docker_compose.hostname, local_service_docker_compose.service_name, False)
|
237
|
+
TestScaffoldE2e.cli_service_create_assets(runner, app_dir_path, assets_service_docker_compose.hostname, assets_service_docker_compose.service_name, False)
|
238
|
+
|
239
|
+
# Don't run the local user defined service in the 'test' workflow
|
240
|
+
# So don't copy the Docker Compose file over
|
241
|
+
|
242
|
+
# Add user defined Docker Compose file for the assets service
|
243
|
+
destination_path = Path(assets_service_docker_compose.docker_compose_path)
|
244
|
+
assert destination_path.is_file()
|
245
|
+
shutil.copyfile(assets_service_mock_docker_compose_path, destination_path)
|
246
|
+
|
247
|
+
TestScaffoldE2e.cli_service_create_assets(runner, app_dir_path, assets_service_docker_compose.hostname, assets_service_docker_compose.service_name, False)
|
248
|
+
|
249
|
+
# Add assets for assets service
|
250
|
+
data_dir_path = DataDir.instance().path
|
251
|
+
destination_assets_path = f"{data_dir_path}/docker/{assets_service_docker_compose.service_name}/{target_workflow_name}/index.html"
|
252
|
+
destination_path = Path(destination_assets_path)
|
253
|
+
assets_mock_path = mock_data_directory_path / "scaffold" / "index.html"
|
254
|
+
shutil.copyfile(assets_mock_path, destination_path)
|
255
|
+
|
256
|
+
# Created shared file in fixtures folder
|
257
|
+
app = App(app_dir_path, DOCKER_NAMESPACE)
|
258
|
+
config = {
|
259
|
+
'workflow_name': target_workflow_name,
|
260
|
+
'service_name': external_service_docker_compose.service_name
|
261
|
+
}
|
262
|
+
command = ServiceWorkflowValidateCommand(app, **config)
|
263
|
+
with open(f"{command.fixtures_dir_path}/shared_file.txt", 'w') as file:
|
264
|
+
file.write('this is a shared file')
|
265
|
+
|
266
|
+
TestScaffoldE2e.cli_workflow_run(runner, app_dir_path, target_workflow_name=target_workflow_name)
|
267
|
+
|
268
|
+
@pytest.fixture(scope="class", autouse=True)
|
269
|
+
def cleanup_after_all(self, runner, app_dir_path, target_workflow_name):
|
270
|
+
yield
|
271
|
+
TestScaffoldE2e.cli_workflow_stop(runner, app_dir_path, target_workflow_name)
|
272
|
+
|
273
|
+
def test_no_core_components(self, app_dir_path, target_workflow_name):
|
274
|
+
app = App(app_dir_path, DOCKER_NAMESPACE)
|
275
|
+
config = {
|
276
|
+
'workflow_name': target_workflow_name,
|
277
|
+
'service_name': 'build'
|
278
|
+
}
|
279
|
+
|
280
|
+
command = WorkflowValidateCommand(app, **config)
|
281
|
+
command.validate()
|
282
|
+
|
283
|
+
def test_user_services(self, app_dir_path, target_workflow_name, external_service_docker_compose, local_service_docker_compose):
|
284
|
+
app = App(app_dir_path, DOCKER_NAMESPACE)
|
285
|
+
|
286
|
+
config = {
|
287
|
+
'workflow_name': target_workflow_name,
|
288
|
+
'service_name': external_service_docker_compose.service_name
|
289
|
+
}
|
290
|
+
command = ServiceWorkflowValidateCommand(app, **config)
|
291
|
+
command.validate()
|
292
|
+
|
293
|
+
config = {
|
294
|
+
'workflow_name': target_workflow_name,
|
295
|
+
'service_name': local_service_docker_compose.service_name
|
296
|
+
}
|
297
|
+
command = ServiceWorkflowValidateCommand(app, **config)
|
298
|
+
command.validate()
|
299
|
+
|
300
|
+
def test_tests(self):
|
301
|
+
# This is covered by test_assets
|
302
|
+
pass
|
303
|
+
|
304
|
+
def test_assets(self, app_dir_path, target_workflow_name):
|
305
|
+
|
306
|
+
app = App(app_dir_path, DOCKER_NAMESPACE)
|
307
|
+
config = {
|
308
|
+
'workflow_name': target_workflow_name,
|
309
|
+
'service_name': 'assets'
|
310
|
+
}
|
311
|
+
|
312
|
+
command = ServiceWorkflowValidateCommand(app, **config)
|
313
|
+
command.validate()
|
314
|
+
|
315
|
+
@staticmethod
|
316
|
+
def cli_app_create(runner: CliRunner, app_dir_path: str, app_name: str):
|
317
|
+
pathlib.Path(f"{app_dir_path}/{DATA_DIR_NAME}").mkdir(parents=True, exist_ok=True)
|
318
|
+
|
319
|
+
result = runner.invoke(scaffold, ['app', 'create',
|
320
|
+
'--app-dir-path', app_dir_path,
|
321
|
+
'--force',
|
322
|
+
app_name
|
323
|
+
])
|
324
|
+
|
325
|
+
assert result.exit_code == 0
|
326
|
+
output = result.stdout
|
327
|
+
assert not output
|
328
|
+
|
329
|
+
@staticmethod
|
330
|
+
def cli_app_mkcert(runner: CliRunner, app_dir_path: str):
|
331
|
+
result = runner.invoke(scaffold, ['app', 'mkcert',
|
332
|
+
'--app-dir-path', app_dir_path,
|
333
|
+
'--context-dir-path', app_dir_path,
|
334
|
+
])
|
335
|
+
|
336
|
+
assert result.exit_code == 0
|
337
|
+
output = result.stdout
|
338
|
+
assert not output
|
339
|
+
|
340
|
+
|
341
|
+
@staticmethod
|
342
|
+
def cli_service_create(runner: CliRunner, app_dir_path: str, hostname: str, service_name: str, https: bool):
|
343
|
+
scheme = 'http'
|
344
|
+
port = '80'
|
345
|
+
if https == True:
|
346
|
+
scheme = 'https'
|
347
|
+
port = '443'
|
348
|
+
|
349
|
+
result = runner.invoke(scaffold, ['service', 'create',
|
350
|
+
'--app-dir-path', app_dir_path,
|
351
|
+
'--env', 'TEST',
|
352
|
+
'--force',
|
353
|
+
'--hostname', hostname,
|
354
|
+
'--scheme', scheme,
|
355
|
+
'--port', port,
|
356
|
+
'--workflow', 'mock',
|
357
|
+
'--workflow', 'record',
|
358
|
+
'--workflow', 'test',
|
359
|
+
service_name
|
360
|
+
])
|
361
|
+
assert result.exit_code == 0
|
362
|
+
output = result.stdout
|
363
|
+
assert not output
|
364
|
+
|
365
|
+
# Specific flags for assets
|
366
|
+
@staticmethod
|
367
|
+
def cli_service_create_assets(runner: CliRunner, app_dir_path: str, hostname: str, service_name: str, https: bool):
|
368
|
+
scheme = 'http'
|
369
|
+
port = '80'
|
370
|
+
if https == True:
|
371
|
+
scheme = 'https'
|
372
|
+
port = '443'
|
373
|
+
proxy_mode_reverse_spec = f"reverse:{scheme}://{hostname}:8080"
|
374
|
+
|
375
|
+
result = runner.invoke(scaffold, ['service', 'create',
|
376
|
+
'--app-dir-path', app_dir_path,
|
377
|
+
'--force',
|
378
|
+
'--hostname', hostname,
|
379
|
+
'--scheme', scheme,
|
380
|
+
'--port', port,
|
381
|
+
'--proxy-mode', proxy_mode_reverse_spec,
|
382
|
+
'--detached',
|
383
|
+
'--workflow', 'test',
|
384
|
+
service_name
|
385
|
+
])
|
386
|
+
assert result.exit_code == 0
|
387
|
+
output = result.stdout
|
388
|
+
assert not output
|
389
|
+
|
390
|
+
@staticmethod
|
391
|
+
def cli_workflow_create(runner: CliRunner, app_dir_path: str, service_name: str):
|
392
|
+
result = runner.invoke(scaffold, ['workflow', 'create',
|
393
|
+
'--app-dir-path', app_dir_path,
|
394
|
+
'--service', service_name,
|
395
|
+
'--template', 'mock',
|
396
|
+
'ci',
|
397
|
+
])
|
398
|
+
|
399
|
+
assert result.exit_code == 0
|
400
|
+
output = result.stdout
|
401
|
+
assert not output
|
402
|
+
|
403
|
+
@staticmethod
|
404
|
+
def cli_workflow_run(runner: CliRunner, app_dir_path: str, target_workflow_name: str):
|
405
|
+
command = ['workflow', 'run',
|
406
|
+
'--app-dir-path', app_dir_path,
|
407
|
+
'--context-dir-path', app_dir_path,
|
408
|
+
target_workflow_name,
|
409
|
+
]
|
410
|
+
result = runner.invoke(scaffold, command)
|
411
|
+
|
412
|
+
assert result.exit_code == 0
|
413
|
+
output = result.stdout
|
414
|
+
assert output
|
415
|
+
|
416
|
+
@staticmethod
|
417
|
+
def cli_workflow_stop(runner: CliRunner, app_dir_path: str, target_workflow_name: str):
|
418
|
+
command = ['workflow', 'stop',
|
419
|
+
'--app-dir-path', app_dir_path,
|
420
|
+
'--context-dir-path', app_dir_path,
|
421
|
+
target_workflow_name,
|
422
|
+
]
|
423
|
+
result = runner.invoke(scaffold, command)
|
424
|
+
|
425
|
+
assert result.exit_code == 0
|
426
|
+
output = result.stdout
|
427
|
+
assert output
|
428
|
+
|
@@ -1 +1 @@
|
|
1
|
-
1.0.
|
1
|
+
1.0.7
|