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.
- stoobly_agent/__init__.py +1 -1
- stoobly_agent/app/cli/__init__.py +1 -0
- stoobly_agent/app/cli/helpers/openapi_endpoint_adapter.py +9 -7
- stoobly_agent/app/cli/helpers/shell.py +28 -0
- stoobly_agent/app/cli/main_group.py +1 -1
- stoobly_agent/app/cli/scaffold/__init__.py +0 -0
- stoobly_agent/app/cli/scaffold/app.py +106 -0
- stoobly_agent/app/cli/scaffold/app_command.py +82 -0
- stoobly_agent/app/cli/scaffold/app_config.py +32 -0
- stoobly_agent/app/cli/scaffold/app_create_command.py +24 -0
- stoobly_agent/app/cli/scaffold/command.py +15 -0
- stoobly_agent/app/cli/scaffold/config.py +35 -0
- stoobly_agent/app/cli/scaffold/constants.py +35 -0
- stoobly_agent/app/cli/scaffold/docker/__init__.py +0 -0
- stoobly_agent/app/cli/scaffold/docker/app_builder.py +26 -0
- stoobly_agent/app/cli/scaffold/docker/builder.py +117 -0
- stoobly_agent/app/cli/scaffold/docker/constants.py +7 -0
- stoobly_agent/app/cli/scaffold/docker/service/__init__.py +0 -0
- stoobly_agent/app/cli/scaffold/docker/service/build_decorator.py +37 -0
- stoobly_agent/app/cli/scaffold/docker/service/builder.py +117 -0
- stoobly_agent/app/cli/scaffold/docker/service/set_gateway_ports.py +47 -0
- stoobly_agent/app/cli/scaffold/docker/service/types.py +4 -0
- stoobly_agent/app/cli/scaffold/docker/workflow/__init__.py +0 -0
- stoobly_agent/app/cli/scaffold/docker/workflow/build_decorator.py +28 -0
- stoobly_agent/app/cli/scaffold/docker/workflow/builder.py +259 -0
- stoobly_agent/app/cli/scaffold/docker/workflow/decorators_factory.py +17 -0
- stoobly_agent/app/cli/scaffold/docker/workflow/mock_decorator.py +40 -0
- stoobly_agent/app/cli/scaffold/docker/workflow/reverse_proxy_decorator.py +51 -0
- stoobly_agent/app/cli/scaffold/env.py +49 -0
- stoobly_agent/app/cli/scaffold/service.py +25 -0
- stoobly_agent/app/cli/scaffold/service_command.py +50 -0
- stoobly_agent/app/cli/scaffold/service_config.py +207 -0
- stoobly_agent/app/cli/scaffold/service_create_command.py +77 -0
- stoobly_agent/app/cli/scaffold/service_workflow.py +18 -0
- stoobly_agent/app/cli/scaffold/templates/__init__.py +0 -0
- stoobly_agent/app/cli/scaffold/templates/app/.Dockerfile.context +8 -0
- stoobly_agent/app/cli/scaffold/templates/app/.Dockerfile.proxy +35 -0
- stoobly_agent/app/cli/scaffold/templates/app/.Makefile +118 -0
- stoobly_agent/app/cli/scaffold/templates/app/.docker-compose.base.yml +15 -0
- stoobly_agent/app/cli/scaffold/templates/app/Makefile +3 -0
- stoobly_agent/app/cli/scaffold/templates/app/build/.config.yml +1 -0
- stoobly_agent/app/cli/scaffold/templates/app/build/.docker-compose.base.yml +11 -0
- stoobly_agent/app/cli/scaffold/templates/app/build/mock/.docker-compose.mock.yml +22 -0
- stoobly_agent/app/cli/scaffold/templates/app/build/mock/bin/.configure +5 -0
- stoobly_agent/app/cli/scaffold/templates/app/build/mock/bin/.init +5 -0
- stoobly_agent/app/cli/scaffold/templates/app/build/mock/bin/configure +1 -0
- stoobly_agent/app/cli/scaffold/templates/app/build/mock/bin/init +1 -0
- stoobly_agent/app/cli/scaffold/templates/app/build/record/.docker-compose.record.yml +22 -0
- stoobly_agent/app/cli/scaffold/templates/app/build/record/bin/.configure +5 -0
- stoobly_agent/app/cli/scaffold/templates/app/build/record/bin/.init +5 -0
- stoobly_agent/app/cli/scaffold/templates/app/build/record/bin/configure +1 -0
- stoobly_agent/app/cli/scaffold/templates/app/build/record/bin/init +1 -0
- stoobly_agent/app/cli/scaffold/templates/app/build/test/.docker-compose.test.yml +22 -0
- stoobly_agent/app/cli/scaffold/templates/app/build/test/bin/.configure +5 -0
- stoobly_agent/app/cli/scaffold/templates/app/build/test/bin/.init +5 -0
- stoobly_agent/app/cli/scaffold/templates/app/build/test/bin/configure +1 -0
- stoobly_agent/app/cli/scaffold/templates/app/build/test/bin/init +1 -0
- stoobly_agent/app/cli/scaffold/templates/app/entrypoint/.config.yml +1 -0
- stoobly_agent/app/cli/scaffold/templates/app/entrypoint/.docker-compose.base.yml +17 -0
- stoobly_agent/app/cli/scaffold/templates/app/entrypoint/mock/.docker-compose.mock.yml +31 -0
- stoobly_agent/app/cli/scaffold/templates/app/entrypoint/mock/bin/.configure +10 -0
- stoobly_agent/app/cli/scaffold/templates/app/entrypoint/mock/bin/.init +5 -0
- stoobly_agent/app/cli/scaffold/templates/app/entrypoint/mock/bin/configure +1 -0
- stoobly_agent/app/cli/scaffold/templates/app/entrypoint/mock/bin/init +4 -0
- stoobly_agent/app/cli/scaffold/templates/app/entrypoint/mock/docker-compose.yml +1 -0
- stoobly_agent/app/cli/scaffold/templates/app/entrypoint/record/.docker-compose.record.yml +31 -0
- stoobly_agent/app/cli/scaffold/templates/app/entrypoint/record/bin/.configure +10 -0
- stoobly_agent/app/cli/scaffold/templates/app/entrypoint/record/bin/.init +5 -0
- stoobly_agent/app/cli/scaffold/templates/app/entrypoint/record/bin/configure +1 -0
- stoobly_agent/app/cli/scaffold/templates/app/entrypoint/record/bin/init +4 -0
- stoobly_agent/app/cli/scaffold/templates/app/entrypoint/record/docker-compose.yml +1 -0
- stoobly_agent/app/cli/scaffold/templates/app/entrypoint/test/.docker-compose.test.yml +31 -0
- stoobly_agent/app/cli/scaffold/templates/app/entrypoint/test/bin/.configure +10 -0
- stoobly_agent/app/cli/scaffold/templates/app/entrypoint/test/bin/.init +3 -0
- stoobly_agent/app/cli/scaffold/templates/app/entrypoint/test/bin/configure +1 -0
- stoobly_agent/app/cli/scaffold/templates/app/entrypoint/test/bin/init +4 -0
- stoobly_agent/app/cli/scaffold/templates/app/entrypoint/test/docker-compose.yml +1 -0
- stoobly_agent/app/cli/scaffold/templates/app/gateway/.config.yml +1 -0
- stoobly_agent/app/cli/scaffold/templates/app/gateway/.docker-compose.base.yml +11 -0
- stoobly_agent/app/cli/scaffold/templates/app/gateway/mock/.docker-compose.mock.yml +13 -0
- stoobly_agent/app/cli/scaffold/templates/app/gateway/record/.docker-compose.record.yml +13 -0
- stoobly_agent/app/cli/scaffold/templates/app/stoobly-ui/.config.yml +1 -0
- stoobly_agent/app/cli/scaffold/templates/app/stoobly-ui/.docker-compose.base.yml +5 -0
- stoobly_agent/app/cli/scaffold/templates/app/stoobly-ui/exec/.docker-compose.exec.yml +12 -0
- stoobly_agent/app/cli/scaffold/templates/app/stoobly-ui/exec/bin/.create +11 -0
- stoobly_agent/app/cli/scaffold/templates/app/stoobly-ui/exec/bin/.delete +12 -0
- stoobly_agent/app/cli/scaffold/templates/app/stoobly-ui/exec/bin/.disable +3 -0
- stoobly_agent/app/cli/scaffold/templates/app/stoobly-ui/exec/bin/.enable +10 -0
- stoobly_agent/app/cli/scaffold/templates/app/stoobly-ui/exec/bin/.reset +12 -0
- stoobly_agent/app/cli/scaffold/templates/app/stoobly-ui/exec/bin/.run +11 -0
- stoobly_agent/app/cli/scaffold/templates/app/stoobly-ui/exec/bin/.snapshot +12 -0
- stoobly_agent/app/cli/scaffold/templates/app/stoobly-ui/exec/bin/.stop +11 -0
- stoobly_agent/app/cli/scaffold/templates/app/stoobly-ui/mock/.docker-compose.mock.yml +12 -0
- stoobly_agent/app/cli/scaffold/templates/app/stoobly-ui/record/.docker-compose.record.yml +13 -0
- stoobly_agent/app/cli/scaffold/templates/constants.py +63 -0
- stoobly_agent/app/cli/scaffold/templates/factory.py +46 -0
- stoobly_agent/app/cli/scaffold/templates/workflow/mock/bin/.configure +3 -0
- stoobly_agent/app/cli/scaffold/templates/workflow/mock/bin/.init +3 -0
- stoobly_agent/app/cli/scaffold/templates/workflow/mock/bin/configure +18 -0
- stoobly_agent/app/cli/scaffold/templates/workflow/mock/bin/init +4 -0
- stoobly_agent/app/cli/scaffold/templates/workflow/mock/fixtures/.keep +0 -0
- stoobly_agent/app/cli/scaffold/templates/workflow/mock/fixtures.yml +5 -0
- stoobly_agent/app/cli/scaffold/templates/workflow/mock/lifecycle_hooks.py +12 -0
- stoobly_agent/app/cli/scaffold/templates/workflow/record/bin/.configure +3 -0
- stoobly_agent/app/cli/scaffold/templates/workflow/record/bin/.init +3 -0
- stoobly_agent/app/cli/scaffold/templates/workflow/record/bin/configure +29 -0
- stoobly_agent/app/cli/scaffold/templates/workflow/record/bin/init +4 -0
- stoobly_agent/app/cli/scaffold/templates/workflow/record/lifecycle_hooks.py +12 -0
- stoobly_agent/app/cli/scaffold/templates/workflow/test/bin/.configure +3 -0
- stoobly_agent/app/cli/scaffold/templates/workflow/test/bin/.init +3 -0
- stoobly_agent/app/cli/scaffold/templates/workflow/test/bin/configure +18 -0
- stoobly_agent/app/cli/scaffold/templates/workflow/test/bin/init +4 -0
- stoobly_agent/app/cli/scaffold/templates/workflow/test/fixtures/.keep +0 -0
- stoobly_agent/app/cli/scaffold/templates/workflow/test/fixtures.yml +5 -0
- stoobly_agent/app/cli/scaffold/templates/workflow/test/lifecycle_hooks.py +12 -0
- stoobly_agent/app/cli/scaffold/workflow.py +49 -0
- stoobly_agent/app/cli/scaffold/workflow_command.py +137 -0
- stoobly_agent/app/cli/scaffold/workflow_copy_command.py +45 -0
- stoobly_agent/app/cli/scaffold/workflow_create_command.py +94 -0
- stoobly_agent/app/cli/scaffold/workflow_log_command.py +21 -0
- stoobly_agent/app/cli/scaffold/workflow_run_command.py +134 -0
- stoobly_agent/app/cli/scaffold_cli.py +392 -0
- stoobly_agent/cli.py +3 -2
- stoobly_agent/config/data_dir.py +40 -14
- stoobly_agent/lib/logger.py +3 -2
- stoobly_agent/test/app/models/schemas/.stoobly/db/VERSION +1 -1
- stoobly_agent/test/config/data_dir_test.py +4 -4
- {stoobly_agent-0.34.13.dist-info → stoobly_agent-1.0.0.dist-info}/METADATA +8 -7
- {stoobly_agent-0.34.13.dist-info → stoobly_agent-1.0.0.dist-info}/RECORD +132 -14
- {stoobly_agent-0.34.13.dist-info → stoobly_agent-1.0.0.dist-info}/WHEEL +1 -1
- {stoobly_agent-0.34.13.dist-info → stoobly_agent-1.0.0.dist-info}/LICENSE +0 -0
- {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.
|
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
|
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
|
-
|
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:
|
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:
|
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:
|
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:
|
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,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'
|
File without changes
|
@@ -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
|
+
}
|