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.
Files changed (34) hide show
  1. stoobly_agent/__init__.py +1 -1
  2. stoobly_agent/app/cli/scaffold/constants.py +9 -1
  3. stoobly_agent/app/cli/scaffold/docker/constants.py +6 -0
  4. stoobly_agent/app/cli/scaffold/docker/workflow/builder.py +0 -2
  5. stoobly_agent/app/cli/scaffold/docker/workflow/mock_decorator.py +1 -1
  6. stoobly_agent/app/cli/scaffold/docker/workflow/reverse_proxy_decorator.py +0 -1
  7. stoobly_agent/app/cli/scaffold/managed_services_docker_compose.py +9 -0
  8. stoobly_agent/app/cli/scaffold/service_command.py +3 -1
  9. stoobly_agent/app/cli/scaffold/service_config.py +2 -1
  10. stoobly_agent/app/cli/scaffold/service_docker_compose.py +15 -0
  11. stoobly_agent/app/cli/scaffold/service_workflow_validate_command.py +234 -0
  12. stoobly_agent/app/cli/scaffold/templates/app/.Makefile +2 -2
  13. stoobly_agent/app/cli/scaffold/templates/app/entrypoint/mock/docker-compose.yml +14 -1
  14. stoobly_agent/app/cli/scaffold/templates/app/entrypoint/record/docker-compose.yml +14 -1
  15. stoobly_agent/app/cli/scaffold/templates/app/entrypoint/test/docker-compose.yml +14 -1
  16. stoobly_agent/app/cli/scaffold/templates/app/stoobly-ui/exec/bin/.run +2 -0
  17. stoobly_agent/app/cli/scaffold/templates/constants.py +3 -3
  18. stoobly_agent/app/cli/scaffold/validate_command.py +59 -0
  19. stoobly_agent/app/cli/scaffold/validate_exceptions.py +5 -0
  20. stoobly_agent/app/cli/scaffold/workflow.py +22 -1
  21. stoobly_agent/app/cli/scaffold/workflow_run_command.py +17 -3
  22. stoobly_agent/app/cli/scaffold/workflow_validate_command.py +94 -0
  23. stoobly_agent/app/cli/scaffold_cli.py +48 -5
  24. stoobly_agent/config/data_dir.py +11 -2
  25. stoobly_agent/test/app/cli/scaffold/e2e_test.py +428 -0
  26. stoobly_agent/test/app/models/schemas/.stoobly/db/VERSION +1 -1
  27. stoobly_agent/test/mock_data/scaffold/docker-compose-assets-service.yml +18 -0
  28. stoobly_agent/test/mock_data/scaffold/docker-compose-local-service.yml +16 -0
  29. stoobly_agent/test/mock_data/scaffold/index.html +12 -0
  30. {stoobly_agent-1.0.7.dist-info → stoobly_agent-1.0.9.dist-info}/METADATA +2 -1
  31. {stoobly_agent-1.0.7.dist-info → stoobly_agent-1.0.9.dist-info}/RECORD +34 -24
  32. {stoobly_agent-1.0.7.dist-info → stoobly_agent-1.0.9.dist-info}/LICENSE +0 -0
  33. {stoobly_agent-1.0.7.dist-info → stoobly_agent-1.0.9.dist-info}/WHEEL +0 -0
  34. {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('--network', help='Name of network namespace.')
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
- exec_command = command.up()
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
+
@@ -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.mkdir(os.path.join(self.__data_dir_path, 'tmp'))
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.5
1
+ 1.0.7