stoobly-agent 0.34.13__py3-none-any.whl → 1.0.0__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 (132) hide show
  1. stoobly_agent/__init__.py +1 -1
  2. stoobly_agent/app/cli/__init__.py +1 -0
  3. stoobly_agent/app/cli/helpers/openapi_endpoint_adapter.py +9 -7
  4. stoobly_agent/app/cli/helpers/shell.py +28 -0
  5. stoobly_agent/app/cli/main_group.py +1 -1
  6. stoobly_agent/app/cli/scaffold/__init__.py +0 -0
  7. stoobly_agent/app/cli/scaffold/app.py +106 -0
  8. stoobly_agent/app/cli/scaffold/app_command.py +82 -0
  9. stoobly_agent/app/cli/scaffold/app_config.py +32 -0
  10. stoobly_agent/app/cli/scaffold/app_create_command.py +24 -0
  11. stoobly_agent/app/cli/scaffold/command.py +15 -0
  12. stoobly_agent/app/cli/scaffold/config.py +35 -0
  13. stoobly_agent/app/cli/scaffold/constants.py +35 -0
  14. stoobly_agent/app/cli/scaffold/docker/__init__.py +0 -0
  15. stoobly_agent/app/cli/scaffold/docker/app_builder.py +26 -0
  16. stoobly_agent/app/cli/scaffold/docker/builder.py +117 -0
  17. stoobly_agent/app/cli/scaffold/docker/constants.py +7 -0
  18. stoobly_agent/app/cli/scaffold/docker/service/__init__.py +0 -0
  19. stoobly_agent/app/cli/scaffold/docker/service/build_decorator.py +37 -0
  20. stoobly_agent/app/cli/scaffold/docker/service/builder.py +117 -0
  21. stoobly_agent/app/cli/scaffold/docker/service/set_gateway_ports.py +47 -0
  22. stoobly_agent/app/cli/scaffold/docker/service/types.py +4 -0
  23. stoobly_agent/app/cli/scaffold/docker/workflow/__init__.py +0 -0
  24. stoobly_agent/app/cli/scaffold/docker/workflow/build_decorator.py +28 -0
  25. stoobly_agent/app/cli/scaffold/docker/workflow/builder.py +259 -0
  26. stoobly_agent/app/cli/scaffold/docker/workflow/decorators_factory.py +17 -0
  27. stoobly_agent/app/cli/scaffold/docker/workflow/mock_decorator.py +40 -0
  28. stoobly_agent/app/cli/scaffold/docker/workflow/reverse_proxy_decorator.py +51 -0
  29. stoobly_agent/app/cli/scaffold/env.py +49 -0
  30. stoobly_agent/app/cli/scaffold/service.py +25 -0
  31. stoobly_agent/app/cli/scaffold/service_command.py +50 -0
  32. stoobly_agent/app/cli/scaffold/service_config.py +207 -0
  33. stoobly_agent/app/cli/scaffold/service_create_command.py +77 -0
  34. stoobly_agent/app/cli/scaffold/service_workflow.py +18 -0
  35. stoobly_agent/app/cli/scaffold/templates/__init__.py +0 -0
  36. stoobly_agent/app/cli/scaffold/templates/app/.Dockerfile.context +8 -0
  37. stoobly_agent/app/cli/scaffold/templates/app/.Dockerfile.proxy +35 -0
  38. stoobly_agent/app/cli/scaffold/templates/app/.Makefile +118 -0
  39. stoobly_agent/app/cli/scaffold/templates/app/.docker-compose.base.yml +15 -0
  40. stoobly_agent/app/cli/scaffold/templates/app/Makefile +3 -0
  41. stoobly_agent/app/cli/scaffold/templates/app/build/.config.yml +1 -0
  42. stoobly_agent/app/cli/scaffold/templates/app/build/.docker-compose.base.yml +11 -0
  43. stoobly_agent/app/cli/scaffold/templates/app/build/mock/.docker-compose.mock.yml +22 -0
  44. stoobly_agent/app/cli/scaffold/templates/app/build/mock/bin/.configure +5 -0
  45. stoobly_agent/app/cli/scaffold/templates/app/build/mock/bin/.init +5 -0
  46. stoobly_agent/app/cli/scaffold/templates/app/build/mock/bin/configure +1 -0
  47. stoobly_agent/app/cli/scaffold/templates/app/build/mock/bin/init +1 -0
  48. stoobly_agent/app/cli/scaffold/templates/app/build/record/.docker-compose.record.yml +22 -0
  49. stoobly_agent/app/cli/scaffold/templates/app/build/record/bin/.configure +5 -0
  50. stoobly_agent/app/cli/scaffold/templates/app/build/record/bin/.init +5 -0
  51. stoobly_agent/app/cli/scaffold/templates/app/build/record/bin/configure +1 -0
  52. stoobly_agent/app/cli/scaffold/templates/app/build/record/bin/init +1 -0
  53. stoobly_agent/app/cli/scaffold/templates/app/build/test/.docker-compose.test.yml +22 -0
  54. stoobly_agent/app/cli/scaffold/templates/app/build/test/bin/.configure +5 -0
  55. stoobly_agent/app/cli/scaffold/templates/app/build/test/bin/.init +5 -0
  56. stoobly_agent/app/cli/scaffold/templates/app/build/test/bin/configure +1 -0
  57. stoobly_agent/app/cli/scaffold/templates/app/build/test/bin/init +1 -0
  58. stoobly_agent/app/cli/scaffold/templates/app/entrypoint/.config.yml +1 -0
  59. stoobly_agent/app/cli/scaffold/templates/app/entrypoint/.docker-compose.base.yml +17 -0
  60. stoobly_agent/app/cli/scaffold/templates/app/entrypoint/mock/.docker-compose.mock.yml +31 -0
  61. stoobly_agent/app/cli/scaffold/templates/app/entrypoint/mock/bin/.configure +10 -0
  62. stoobly_agent/app/cli/scaffold/templates/app/entrypoint/mock/bin/.init +5 -0
  63. stoobly_agent/app/cli/scaffold/templates/app/entrypoint/mock/bin/configure +1 -0
  64. stoobly_agent/app/cli/scaffold/templates/app/entrypoint/mock/bin/init +4 -0
  65. stoobly_agent/app/cli/scaffold/templates/app/entrypoint/mock/docker-compose.yml +1 -0
  66. stoobly_agent/app/cli/scaffold/templates/app/entrypoint/record/.docker-compose.record.yml +31 -0
  67. stoobly_agent/app/cli/scaffold/templates/app/entrypoint/record/bin/.configure +10 -0
  68. stoobly_agent/app/cli/scaffold/templates/app/entrypoint/record/bin/.init +5 -0
  69. stoobly_agent/app/cli/scaffold/templates/app/entrypoint/record/bin/configure +1 -0
  70. stoobly_agent/app/cli/scaffold/templates/app/entrypoint/record/bin/init +4 -0
  71. stoobly_agent/app/cli/scaffold/templates/app/entrypoint/record/docker-compose.yml +1 -0
  72. stoobly_agent/app/cli/scaffold/templates/app/entrypoint/test/.docker-compose.test.yml +31 -0
  73. stoobly_agent/app/cli/scaffold/templates/app/entrypoint/test/bin/.configure +10 -0
  74. stoobly_agent/app/cli/scaffold/templates/app/entrypoint/test/bin/.init +3 -0
  75. stoobly_agent/app/cli/scaffold/templates/app/entrypoint/test/bin/configure +1 -0
  76. stoobly_agent/app/cli/scaffold/templates/app/entrypoint/test/bin/init +4 -0
  77. stoobly_agent/app/cli/scaffold/templates/app/entrypoint/test/docker-compose.yml +1 -0
  78. stoobly_agent/app/cli/scaffold/templates/app/gateway/.config.yml +1 -0
  79. stoobly_agent/app/cli/scaffold/templates/app/gateway/.docker-compose.base.yml +11 -0
  80. stoobly_agent/app/cli/scaffold/templates/app/gateway/mock/.docker-compose.mock.yml +13 -0
  81. stoobly_agent/app/cli/scaffold/templates/app/gateway/record/.docker-compose.record.yml +13 -0
  82. stoobly_agent/app/cli/scaffold/templates/app/stoobly-ui/.config.yml +1 -0
  83. stoobly_agent/app/cli/scaffold/templates/app/stoobly-ui/.docker-compose.base.yml +5 -0
  84. stoobly_agent/app/cli/scaffold/templates/app/stoobly-ui/exec/.docker-compose.exec.yml +12 -0
  85. stoobly_agent/app/cli/scaffold/templates/app/stoobly-ui/exec/bin/.create +11 -0
  86. stoobly_agent/app/cli/scaffold/templates/app/stoobly-ui/exec/bin/.delete +12 -0
  87. stoobly_agent/app/cli/scaffold/templates/app/stoobly-ui/exec/bin/.disable +3 -0
  88. stoobly_agent/app/cli/scaffold/templates/app/stoobly-ui/exec/bin/.enable +10 -0
  89. stoobly_agent/app/cli/scaffold/templates/app/stoobly-ui/exec/bin/.reset +12 -0
  90. stoobly_agent/app/cli/scaffold/templates/app/stoobly-ui/exec/bin/.run +11 -0
  91. stoobly_agent/app/cli/scaffold/templates/app/stoobly-ui/exec/bin/.snapshot +12 -0
  92. stoobly_agent/app/cli/scaffold/templates/app/stoobly-ui/exec/bin/.stop +11 -0
  93. stoobly_agent/app/cli/scaffold/templates/app/stoobly-ui/mock/.docker-compose.mock.yml +12 -0
  94. stoobly_agent/app/cli/scaffold/templates/app/stoobly-ui/record/.docker-compose.record.yml +13 -0
  95. stoobly_agent/app/cli/scaffold/templates/constants.py +63 -0
  96. stoobly_agent/app/cli/scaffold/templates/factory.py +46 -0
  97. stoobly_agent/app/cli/scaffold/templates/workflow/mock/bin/.configure +3 -0
  98. stoobly_agent/app/cli/scaffold/templates/workflow/mock/bin/.init +3 -0
  99. stoobly_agent/app/cli/scaffold/templates/workflow/mock/bin/configure +18 -0
  100. stoobly_agent/app/cli/scaffold/templates/workflow/mock/bin/init +4 -0
  101. stoobly_agent/app/cli/scaffold/templates/workflow/mock/fixtures/.keep +0 -0
  102. stoobly_agent/app/cli/scaffold/templates/workflow/mock/fixtures.yml +5 -0
  103. stoobly_agent/app/cli/scaffold/templates/workflow/mock/lifecycle_hooks.py +12 -0
  104. stoobly_agent/app/cli/scaffold/templates/workflow/record/bin/.configure +3 -0
  105. stoobly_agent/app/cli/scaffold/templates/workflow/record/bin/.init +3 -0
  106. stoobly_agent/app/cli/scaffold/templates/workflow/record/bin/configure +29 -0
  107. stoobly_agent/app/cli/scaffold/templates/workflow/record/bin/init +4 -0
  108. stoobly_agent/app/cli/scaffold/templates/workflow/record/lifecycle_hooks.py +12 -0
  109. stoobly_agent/app/cli/scaffold/templates/workflow/test/bin/.configure +3 -0
  110. stoobly_agent/app/cli/scaffold/templates/workflow/test/bin/.init +3 -0
  111. stoobly_agent/app/cli/scaffold/templates/workflow/test/bin/configure +18 -0
  112. stoobly_agent/app/cli/scaffold/templates/workflow/test/bin/init +4 -0
  113. stoobly_agent/app/cli/scaffold/templates/workflow/test/fixtures/.keep +0 -0
  114. stoobly_agent/app/cli/scaffold/templates/workflow/test/fixtures.yml +5 -0
  115. stoobly_agent/app/cli/scaffold/templates/workflow/test/lifecycle_hooks.py +12 -0
  116. stoobly_agent/app/cli/scaffold/workflow.py +49 -0
  117. stoobly_agent/app/cli/scaffold/workflow_command.py +137 -0
  118. stoobly_agent/app/cli/scaffold/workflow_copy_command.py +45 -0
  119. stoobly_agent/app/cli/scaffold/workflow_create_command.py +94 -0
  120. stoobly_agent/app/cli/scaffold/workflow_log_command.py +21 -0
  121. stoobly_agent/app/cli/scaffold/workflow_run_command.py +134 -0
  122. stoobly_agent/app/cli/scaffold_cli.py +392 -0
  123. stoobly_agent/cli.py +3 -2
  124. stoobly_agent/config/data_dir.py +40 -14
  125. stoobly_agent/lib/logger.py +3 -2
  126. stoobly_agent/test/app/models/schemas/.stoobly/db/VERSION +1 -1
  127. stoobly_agent/test/config/data_dir_test.py +4 -4
  128. {stoobly_agent-0.34.13.dist-info → stoobly_agent-1.0.0.dist-info}/METADATA +8 -7
  129. {stoobly_agent-0.34.13.dist-info → stoobly_agent-1.0.0.dist-info}/RECORD +132 -14
  130. {stoobly_agent-0.34.13.dist-info → stoobly_agent-1.0.0.dist-info}/WHEEL +1 -1
  131. {stoobly_agent-0.34.13.dist-info → stoobly_agent-1.0.0.dist-info}/LICENSE +0 -0
  132. {stoobly_agent-0.34.13.dist-info → stoobly_agent-1.0.0.dist-info}/entry_points.txt +0 -0
stoobly_agent/__init__.py CHANGED
@@ -1,2 +1,2 @@
1
1
  COMMAND = 'stoobly-agent'
2
- VERSION = '0.34.13'
2
+ VERSION = '1.0.0'
@@ -8,6 +8,7 @@ from .main_group import MainGroup
8
8
  from .project_cli import project
9
9
  from .report_cli import report
10
10
  from .request_cli import request
11
+ from .scaffold_cli import scaffold
11
12
  from .scenario_cli import scenario
12
13
  from .snapshot_cli import snapshot
13
14
  from .trace_cli import trace
@@ -4,7 +4,8 @@ import re
4
4
  import yaml
5
5
 
6
6
  from functools import reduce
7
- from openapi_core import Spec
7
+ from jsonschema_path import SchemaPath
8
+ from openapi_core import OpenAPI
8
9
  from pprint import pprint
9
10
  from typing import Dict, List, Union
10
11
  from urllib.parse import urlparse
@@ -35,11 +36,12 @@ class OpenApiEndpointAdapter():
35
36
  if missing_oauth2_scopes:
36
37
  self.__add_oauth2_default_scopes(file_data)
37
38
 
38
- spec = Spec.from_dict(file_data)
39
+ openApi = OpenAPI.from_dict(file_data)
40
+ spec = openApi.spec
39
41
 
40
42
  return self.adapt(spec)
41
43
 
42
- def adapt(self, spec: Spec) -> List[EndpointShowResponse]:
44
+ def adapt(self, spec: SchemaPath) -> List[EndpointShowResponse]:
43
45
  endpoints = []
44
46
  endpoint_counter = 0
45
47
  components = spec.get("components", {})
@@ -364,7 +366,7 @@ class OpenApiEndpointAdapter():
364
366
 
365
367
 
366
368
  # Returns the schema object located at the given reference path
367
- def __dereference(self, components: Spec, reference: str):
369
+ def __dereference(self, components: SchemaPath, reference: str):
368
370
  # '#/components/schemas/NewPet'
369
371
  if not reference.startswith('#'):
370
372
  print('external references are not supported yet')
@@ -475,7 +477,7 @@ class OpenApiEndpointAdapter():
475
477
  num = url.count('{')
476
478
  return num
477
479
 
478
- def __evaluate_servers(self, servers: Spec) -> List[dict]:
480
+ def __evaluate_servers(self, servers: SchemaPath) -> List[dict]:
479
481
  result = []
480
482
 
481
483
  if not servers:
@@ -524,7 +526,7 @@ class OpenApiEndpointAdapter():
524
526
 
525
527
  return result
526
528
 
527
- def __parse_responses(self, endpoint: EndpointShowResponse, responses: Spec, components: Spec):
529
+ def __parse_responses(self, endpoint: EndpointShowResponse, responses: SchemaPath, components: SchemaPath):
528
530
  for response_code, response_definition in responses.items():
529
531
  # Only support status code 200 for now
530
532
  if response_code != '200':
@@ -566,4 +568,4 @@ class OpenApiEndpointAdapter():
566
568
 
567
569
  if not endpoint.get('response_header_names'):
568
570
  endpoint['response_header_names'] = []
569
- endpoint['response_header_names'].append(response_header_name)
571
+ endpoint['response_header_names'].append(response_header_name)
@@ -0,0 +1,28 @@
1
+ import subprocess
2
+ import sys
3
+
4
+ from dotenv import load_dotenv
5
+
6
+ def exec_stream(command):
7
+ load_dotenv()
8
+
9
+ # Start the process
10
+ process = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
11
+
12
+ # Stream output line by line
13
+ while True:
14
+ stdout = process.stdout.readline()
15
+ stderr = process.stderr.readline()
16
+
17
+ if stdout == '' and stderr == '' and process.poll() is not None:
18
+ break
19
+
20
+ if stdout:
21
+ print(stdout, end='')
22
+
23
+ if stderr:
24
+ print(stderr, end='', file=sys.stderr)
25
+
26
+ # Get the remaining output (if any)
27
+ rc = process.poll()
28
+ return rc
@@ -26,7 +26,7 @@ class MainGroup(click.Group):
26
26
  command_groups: List[CommandGroup] = [
27
27
  {
28
28
  'name': 'Commands',
29
- 'commands': ['dev-tools', 'exec', 'feature', 'init', 'mock', 'record'],
29
+ 'commands': ['dev-tools', 'exec', 'feature', 'init', 'mock', 'record', 'scaffold'],
30
30
  },
31
31
  {
32
32
  'name': 'Proxy Commands',
File without changes
@@ -0,0 +1,106 @@
1
+ import os
2
+ import shutil
3
+
4
+ from stoobly_agent.config.data_dir import DataDir, DATA_DIR_NAME
5
+
6
+ class App():
7
+
8
+ def __init__(self, path: str, namespace: str, **kwargs):
9
+ path = path or os.getcwd()
10
+ data_dir: DataDir = DataDir.instance(path)
11
+
12
+ self.__scaffold_dir_path = data_dir.path
13
+ self.__certs_dir_path = os.path.join(data_dir.tmp_dir_path, 'certs')
14
+ self.__context_dir_path = data_dir.context_dir_path
15
+ self.__dir_path = path
16
+ self.__name = os.path.basename(self.__dir_path)
17
+ self.__network = os.path.basename(self.__dir_path)
18
+ self.__namespace = namespace
19
+ self.__skip_validate_path = not not kwargs.get('skip_validate_path')
20
+
21
+ @property
22
+ def certs_dir_path(self):
23
+ return self.__certs_dir_path
24
+
25
+ @certs_dir_path.setter
26
+ def certs_dir_path(self, v: str):
27
+ self.__validate_path(v)
28
+ self.__certs_dir_path = v
29
+
30
+ @property
31
+ def context_dir_path(self):
32
+ return self.__context_dir_path
33
+
34
+ @context_dir_path.setter
35
+ def context_dir_path(self, v: str):
36
+ self.__validate_path(v)
37
+ self.__context_dir_path = v
38
+
39
+ @property
40
+ def data_dir_path(self):
41
+ return os.path.join(self.context_dir_path, DATA_DIR_NAME)
42
+
43
+ @property
44
+ def exists(self):
45
+ return os.path.exists(self.dir_path)
46
+
47
+ @property
48
+ def name(self):
49
+ return self.__name
50
+
51
+ @name.setter
52
+ def name(self, v: str):
53
+ self.__name = v
54
+
55
+ @property
56
+ def network(self):
57
+ return self.__network
58
+
59
+ @network.setter
60
+ def network(self, v: str):
61
+ self.__network = v
62
+
63
+ @property
64
+ def namespace(self):
65
+ return self.__namespace
66
+
67
+ @property
68
+ def namespace_path(self):
69
+ return os.path.join(self.data_dir_path, self.namespace)
70
+
71
+ @property
72
+ def dir_path(self):
73
+ return self.__dir_path
74
+
75
+ @property
76
+ def scaffold_dir_path(self):
77
+ return self.__scaffold_dir_path
78
+
79
+ @property
80
+ def scaffold_namespace_path(self):
81
+ return os.path.join(self.scaffold_dir_path, self.namespace)
82
+
83
+ def copy_folders_and_hidden_files(self, src, dst):
84
+ os.makedirs(dst, exist_ok=True)
85
+
86
+ # Walk through the source directory
87
+ for root, dirs, files in os.walk(src):
88
+ # Copy hidden files only
89
+ for file_name in files:
90
+ src_file_path = os.path.join(root, file_name)
91
+ dst_file_path = os.path.join(dst, os.path.relpath(root, src), file_name)
92
+
93
+ if not file_name.startswith('.'):
94
+ if os.path.exists(dst_file_path):
95
+ continue
96
+
97
+ os.makedirs(os.path.dirname(dst_file_path), exist_ok=True) # Create directories in destination
98
+ shutil.copy2(src_file_path, dst_file_path)
99
+
100
+ def __validate_path(self, v: str):
101
+ if not isinstance(v, str):
102
+ raise TypeError('Expected a str')
103
+
104
+ if not self.__skip_validate_path:
105
+ if not os.path.exists(v):
106
+ raise ValueError(f"{v} does not exist")
@@ -0,0 +1,82 @@
1
+ import os
2
+ import pathlib
3
+ import shutil
4
+
5
+ from .app import App
6
+ from .app_config import AppConfig
7
+ from .command import Command
8
+
9
+ class AppCommand(Command):
10
+
11
+ def __init__(self, app: App):
12
+ super().__init__(app)
13
+
14
+ self.__config = AppConfig(self.scaffold_namespace_path)
15
+ self.__config.network = app.network
16
+
17
+ @property
18
+ def app_dir_path(self):
19
+ return self.app.dir_path
20
+
21
+ @property
22
+ def app_dir_exists(self):
23
+ return os.path.exists(self.app_dir_path)
24
+
25
+ @property
26
+ def app_config(self):
27
+ return self.__config
28
+
29
+ @property
30
+ def app_config_path(self):
31
+ return self.__config.path
32
+
33
+ @property
34
+ def app_namespace_path(self):
35
+ return self.app.namespace_path
36
+
37
+ @property
38
+ def app_templates_root_dir(self):
39
+ return os.path.join(self.templates_root_dir, 'app')
40
+
41
+ @property
42
+ def scaffold_dir_path(self):
43
+ return self.app.scaffold_dir_path
44
+
45
+ @property
46
+ def scaffold_namespace_path(self):
47
+ return self.app.scaffold_namespace_path
48
+
49
+ @property
50
+ def templates_root_dir(self):
51
+ return os.path.join(pathlib.Path(__file__).parent.resolve(), 'templates')
52
+
53
+ def config(self, _c: dict):
54
+ _config = self.app_config.read()
55
+ _config.update(_c)
56
+ return _config
57
+
58
+ def copy_files_no_replace(self, source_dir: str, src_files: list, dest_dir: str):
59
+ return self.copy_files(source_dir, src_files, dest_dir, False)
60
+
61
+ def copy_files(self, source_dir: str, src_files: list, dest_dir: str, replace_ok=True):
62
+ # Ensure the destination directory exists
63
+ if not os.path.exists(dest_dir):
64
+ os.makedirs(dest_dir)
65
+
66
+ for file in src_files:
67
+ src_file_path = os.path.join(source_dir, file)
68
+ if not os.path.isfile(src_file_path):
69
+ continue
70
+
71
+ if not os.path.exists(src_file_path):
72
+ continue
73
+
74
+ dest_subdir = os.path.join(dest_dir, os.path.dirname(file))
75
+
76
+ if os.path.exists(os.path.join(dest_dir, file)) and not replace_ok:
77
+ continue
78
+
79
+ if not os.path.exists(dest_subdir):
80
+ os.makedirs(dest_subdir, exist_ok=True)
81
+
82
+ shutil.copy(src_file_path, dest_subdir)
@@ -0,0 +1,32 @@
1
+ from .config import Config
2
+ from .constants import APP_NETWORK_ENV
3
+
4
+ class AppConfig(Config):
5
+
6
+ def __init__(self, dir: str):
7
+ super().__init__(dir)
8
+
9
+ self.__network = None
10
+
11
+ self.load()
12
+
13
+ @property
14
+ def network(self):
15
+ return self.__network
16
+
17
+ @network.setter
18
+ def network(self, v):
19
+ self.__network = v
20
+
21
+ def load(self, config = None):
22
+ config = config or self.read()
23
+
24
+ self.__network = config.get(APP_NETWORK_ENV)
25
+
26
+ def write(self):
27
+ config = {}
28
+
29
+ if self.network:
30
+ config[APP_NETWORK_ENV] = self.network
31
+
32
+ super().write(config)
@@ -0,0 +1,24 @@
1
+ import os
2
+ import pdb
3
+
4
+ from .app import App
5
+ from .app_command import AppCommand
6
+
7
+ class AppCreateCommand(AppCommand):
8
+
9
+ def __init__(self, app: App, **kwargs):
10
+ super().__init__(app)
11
+
12
+ @property
13
+ def app_name(self):
14
+ return self.app.name
15
+
16
+ def build(self):
17
+ dest = self.scaffold_namespace_path
18
+
19
+ self.app.copy_folders_and_hidden_files(self.app_templates_root_dir, dest)
20
+
21
+ with open(os.path.join(dest, '.gitignore'), 'w') as fp:
22
+ fp.write("\n".join(['**/.env', '**/.config.yml']))
23
+
24
+ self.app_config.write()
@@ -0,0 +1,15 @@
1
+ from .app import App
2
+
3
+ class Command():
4
+
5
+ def __init__(self, app: App):
6
+ self.__app = app
7
+ self.__namespace = app.namespace
8
+
9
+ @property
10
+ def app(self):
11
+ return self.__app
12
+
13
+ @property
14
+ def namespace(self):
15
+ return self.__namespace
@@ -0,0 +1,35 @@
1
+ import os
2
+ import yaml
3
+
4
+ from .constants import CONFIG_FILE
5
+
6
+ class Config():
7
+
8
+ def __init__(self, dir: str, file_name = None):
9
+ self.__dir = dir
10
+ self.__path = os.path.join(dir, file_name or CONFIG_FILE)
11
+
12
+ @property
13
+ def dir(self):
14
+ return self.__dir
15
+
16
+ @property
17
+ def path(self):
18
+ return self.__path
19
+
20
+ def read(self, source: str = None):
21
+ if not source:
22
+ source = self.path
23
+
24
+ config = {}
25
+
26
+ if os.path.exists(source):
27
+ with open(source, 'r') as fp:
28
+ config = yaml.safe_load(fp)
29
+
30
+ return config
31
+
32
+ def write(self, config: dict):
33
+ config_path = os.path.join(self.path)
34
+ with open(config_path, 'w') as fp:
35
+ yaml.dump(config, fp)
@@ -0,0 +1,35 @@
1
+ from typing import Literal
2
+
3
+ APP_NETWORK_ENV = 'APP_NETWORK'
4
+ CERTS_DIR_ENV = 'CERTS_DIR'
5
+ COMPOSE_TEMPLATE = '.docker-compose.{workflow}.yml'
6
+ CONFIG_FILE = '.config.yml'
7
+ CONTEXT_DIR_ENV = 'CONTEXT_DIR'
8
+ DOCKER_NAMESPACE = 'docker'
9
+ ENV_FILE = '.env'
10
+ FIXTURES_FOLDER_NAME = 'fixtures'
11
+ SERVICE_DETACHED = '${SERVICE_DETACHED}'
12
+ SERVICE_DETACHED_ENV = 'SERVICE_DETACHED'
13
+ SERVICE_DOCKER_COMPOSE_PATH = '${SERVICE_DOCKER_COMPOSE_PATH}'
14
+ SERVICE_DOCKER_COMPOSE_PATH_ENV = 'SERVICE_DOCKER_COMPOSE_PATH'
15
+ SERVICE_HOSTNAME = '${SERVICE_HOSTNAME}'
16
+ SERVICE_HOSTNAME_ENV = 'SERVICE_HOSTNAME'
17
+ SERVICE_DNS = '${SERVICE_DNS}'
18
+ SERVICE_DNS_ENV = 'SERVICE_DNS'
19
+ SERVICE_NAME_ENV = 'SERVICE_NAME'
20
+ SERVICE_PROXY_MODE = '${SERVICE_PROXY_MODE}'
21
+ SERVICE_PROXY_MODE_ENV = 'SERVICE_PROXY_MODE'
22
+ SERVICE_SCHEME = '${SERVICE_SCHEME}'
23
+ SERVICE_SCHEME_ENV = 'SERVICE_SCHEME'
24
+ SERVICE_PORT = '${SERVICE_PORT}'
25
+ SERVICE_PORT_ENV = 'SERVICE_PORT'
26
+ SERVICE_PRIORITY_ENV = 'SERVICE_PRIORITY'
27
+ STOOBLY_HOME_DIR = '/home/stoobly'
28
+ USER_ID_ENV = 'USER_ID'
29
+ WORKFLOW_CUSTOM_FILTER = 'custom'
30
+ WORKFLOW_MOCK_TYPE = 'mock'
31
+ WORKFLOW_NAME_ENV = 'WORKFLOW_NAME'
32
+ WORKFLOW_RECORD_TYPE = 'record'
33
+ WORKFLOW_TEST_TYPE = 'test'
34
+
35
+ WORKFLOW_TEMPLATE = Literal[WORKFLOW_MOCK_TYPE, WORKFLOW_RECORD_TYPE, WORKFLOW_TEST_TYPE]
File without changes
@@ -0,0 +1,26 @@
1
+ import os
2
+
3
+ from .builder import Builder
4
+ from .constants import DOCKER_COMPOSE_BASE, DOCKERFILE_CONTEXT, DOCKERFILE_PROXY
5
+ from ..app_config import AppConfig
6
+
7
+ class AppBuilder(Builder):
8
+
9
+ def __init__(self, config: AppConfig):
10
+ super().__init__(config.dir, DOCKER_COMPOSE_BASE)
11
+
12
+ @property
13
+ def context_base(self):
14
+ return 'context_base'
15
+
16
+ @property
17
+ def context_docker_file_path(self):
18
+ return os.path.join(self.dir_path, DOCKERFILE_CONTEXT)
19
+
20
+ @property
21
+ def proxy_base(self):
22
+ return 'proxy_base'
23
+
24
+ @property
25
+ def proxy_docker_file_path(self):
26
+ return os.path.join(self.dir_path, DOCKERFILE_PROXY)
@@ -0,0 +1,117 @@
1
+ import os
2
+ import pathlib
3
+ import pdb
4
+ import yaml
5
+
6
+ from .constants import APP_NETWORK, DOCKER_COMPOSE_CUSTOM, GATEWAY_NETWORK
7
+
8
+ class Builder():
9
+
10
+ def __init__(self, dir_path: str, compose_file_name: str):
11
+ self.__compose_file_name = compose_file_name
12
+ self.__dir_path = dir_path
13
+ self.__networks = {}
14
+ self.__services = {}
15
+ self.__templates_dir = os.path.join(pathlib.Path(__file__).parent.parent.resolve(), 'templates')
16
+ self.__volumes = {}
17
+
18
+ @property
19
+ def compose_file_name(self):
20
+ return self.__compose_file_name
21
+
22
+ @property
23
+ def compose_file_path(self):
24
+ return os.path.join(self.dir_path, self.compose_file_name)
25
+
26
+ @property
27
+ def custom_compose_file_path(self):
28
+ return os.path.join(
29
+ self.dir_path,
30
+ DOCKER_COMPOSE_CUSTOM
31
+ )
32
+
33
+ @property
34
+ def dir_path(self):
35
+ return self.__dir_path
36
+
37
+ @property
38
+ def public_network(self):
39
+ return self.__networks.get(GATEWAY_NETWORK)
40
+
41
+ @property
42
+ def public_network_name(self):
43
+ return GATEWAY_NETWORK
44
+
45
+ @property
46
+ def networks(self):
47
+ return self.__networks
48
+
49
+ @property
50
+ def services(self):
51
+ return self.__services
52
+
53
+ @property
54
+ def templates_dir(self):
55
+ return self.__templates_dir
56
+
57
+ @property
58
+ def volumes(self):
59
+ return self.__volumes
60
+
61
+ def build_extends(self, service_name: str, source_dir: str):
62
+ return {
63
+ 'file': os.path.relpath(self.compose_file_path, source_dir),
64
+ 'service': service_name
65
+ }
66
+
67
+ # If the file already exists, load the specified resources
68
+ def load(self):
69
+ if not os.path.exists(self.compose_file_path):
70
+ return
71
+
72
+ with open(self.compose_file_path, 'r') as fp:
73
+ contents = yaml.safe_load(fp) or {}
74
+
75
+ networks = contents.get('networks')
76
+ if isinstance(networks, dict):
77
+ self.__networks = networks
78
+
79
+ services = contents.get('services')
80
+ if isinstance(services, dict):
81
+ self.__services = services
82
+
83
+ volumes = contents.get('volumes')
84
+ if isinstance(volumes, dict):
85
+ self.__volumes = volumes
86
+
87
+ def with_network(self, network):
88
+ self.__networks[network] = {
89
+ 'name': network
90
+ }
91
+ return self
92
+
93
+ def with_public_network(self, network = APP_NETWORK):
94
+ self.__networks[GATEWAY_NETWORK] = {
95
+ 'external': True,
96
+ 'name': network,
97
+ }
98
+ return self
99
+
100
+ def with_service(self, service_name: str, service: dict):
101
+ self.__services[service_name] = service
102
+ return self
103
+
104
+ def with_volume(self, volume_name: str, volume: dict = {}):
105
+ self.__volumes[volume_name] = volume
106
+ return self
107
+
108
+ def write(self, compose: dict, path = None):
109
+ path = path or self.compose_file_path
110
+ parent_dir = os.path.dirname(path)
111
+
112
+ if not os.path.exists(parent_dir):
113
+ os.makedirs(parent_dir)
114
+
115
+ with open(path, 'w') as fp:
116
+ yaml.dump(compose, fp, allow_unicode=True)
117
+ fp.close()
@@ -0,0 +1,7 @@
1
+ APP_NETWORK = '${APP_NETWORK}'
2
+ DOCKER_COMPOSE_BASE = '.docker-compose.base.yml'
3
+ DOCKER_COMPOSE_CUSTOM = 'docker-compose.yml'
4
+ DOCKERFILE_CONTEXT = '.Dockerfile.context'
5
+ DOCKERFILE_PROXY = '.Dockerfile.proxy'
6
+ DOCKERFILE_SERVICE = 'Dockerfile.source'
7
+ GATEWAY_NETWORK = 'gateway'
@@ -0,0 +1,37 @@
1
+ import pdb
2
+
3
+ from typing import TypedDict
4
+
5
+ from ...constants import DOCKER_NAMESPACE
6
+ from ..constants import DOCKERFILE_SERVICE
7
+ from .builder import ServiceBuilder
8
+ from .types import BuildDecoratorOptions
9
+
10
+ class BuildDecorator():
11
+
12
+ def __init__(self, service_builder: ServiceBuilder):
13
+ self.__service_builder = service_builder
14
+
15
+ def decorate(self, **kwargs: BuildDecoratorOptions):
16
+ service_builder = self.__service_builder
17
+ build = {
18
+ 'context': '../..', # Assumes app root is 2 levels up
19
+ 'dockerfile': f"./{DOCKER_NAMESPACE}/{service_builder.service_name}/{DOCKERFILE_SERVICE}"
20
+ }
21
+
22
+ if 'build_args' in kwargs:
23
+ args = {}
24
+ for arg in kwargs['build_args']:
25
+ args[arg] = '${' + arg + '}'
26
+ build['args'] = args
27
+
28
+ services = self.__service_builder.services
29
+ app_name = self.__service_builder.app_base
30
+ app_service = services.get(app_name) or {}
31
+
32
+ services[app_name] = {
33
+ **app_service,
34
+ **{
35
+ 'build': build,
36
+ }
37
+ }