stoobly-agent 1.3.0__py3-none-any.whl → 1.4.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/api/application_http_request_handler.py +3 -3
- stoobly_agent/app/api/proxy_controller.py +8 -7
- stoobly_agent/app/cli/config_cli.py +1 -1
- stoobly_agent/app/cli/helpers/certificate_authority.py +6 -1
- stoobly_agent/app/cli/helpers/print_service.py +17 -0
- stoobly_agent/app/cli/scaffold/app.py +2 -2
- stoobly_agent/app/cli/scaffold/constants.py +0 -2
- stoobly_agent/app/cli/scaffold/hosts_file_manager.py +112 -0
- stoobly_agent/app/cli/scaffold/service.py +0 -1
- stoobly_agent/app/cli/scaffold/service_config.py +10 -14
- stoobly_agent/app/cli/scaffold/service_workflow_validate_command.py +3 -3
- stoobly_agent/app/cli/scaffold/templates/app/.Dockerfile.context +1 -1
- stoobly_agent/app/cli/scaffold/templates/app/.Makefile +77 -53
- stoobly_agent/app/cli/scaffold/templates/app/stoobly-ui/exec/bin/.services +9 -0
- stoobly_agent/app/cli/scaffold/workflow_create_command.py +0 -1
- stoobly_agent/app/cli/scaffold/workflow_run_command.py +6 -9
- stoobly_agent/app/cli/scaffold_cli.py +200 -69
- stoobly_agent/app/cli/snapshot_cli.py +1 -1
- stoobly_agent/app/proxy/handle_mock_service.py +2 -0
- stoobly_agent/app/proxy/handle_replay_service.py +2 -0
- stoobly_agent/app/proxy/mitmproxy/request_facade.py +1 -1
- stoobly_agent/app/proxy/mitmproxy/response_body_facade.py +19 -0
- stoobly_agent/app/proxy/mitmproxy/response_facade.py +90 -18
- stoobly_agent/app/proxy/record/join_request_service.py +1 -1
- stoobly_agent/app/settings/constants/request_component.py +2 -1
- stoobly_agent/config/constants/custom_headers.py +13 -13
- stoobly_agent/config/constants/headers.py +0 -2
- stoobly_agent/public/18-es2015.583f191cc7ad512ee262.js +1 -0
- stoobly_agent/public/18-es5.583f191cc7ad512ee262.js +1 -0
- stoobly_agent/public/35-es2015.8f79ff8748d4ff06ab03.js +1 -0
- stoobly_agent/public/35-es5.8f79ff8748d4ff06ab03.js +1 -0
- stoobly_agent/public/index.html +1 -1
- stoobly_agent/public/main-es2015.2cc16523aa3fcaba51e5.js +1 -0
- stoobly_agent/public/main-es5.2cc16523aa3fcaba51e5.js +1 -0
- stoobly_agent/public/{runtime-es2015.9addf49b79aca951b7e2.js → runtime-es2015.b914470164e4d6e75d96.js} +1 -1
- stoobly_agent/public/{runtime-es5.9addf49b79aca951b7e2.js → runtime-es5.b914470164e4d6e75d96.js} +1 -1
- stoobly_agent/test/app/cli/scaffold/{hosts_file_reader_test.py → hosts_file_manager_test.py} +20 -20
- stoobly_agent/test/app/models/schemas/.stoobly/db/VERSION +1 -1
- {stoobly_agent-1.3.0.dist-info → stoobly_agent-1.4.0.dist-info}/METADATA +1 -1
- {stoobly_agent-1.3.0.dist-info → stoobly_agent-1.4.0.dist-info}/RECORD +44 -42
- stoobly_agent/app/cli/scaffold/hosts_file_reader.py +0 -65
- stoobly_agent/public/18-es2015.d3b430636a4d6f544d92.js +0 -1
- stoobly_agent/public/18-es5.d3b430636a4d6f544d92.js +0 -1
- stoobly_agent/public/35-es2015.f741ebce0bfc25f0ec99.js +0 -1
- stoobly_agent/public/35-es5.f741ebce0bfc25f0ec99.js +0 -1
- stoobly_agent/public/main-es2015.ccd46ac1b6638ddf2066.js +0 -1
- stoobly_agent/public/main-es5.ccd46ac1b6638ddf2066.js +0 -1
- {stoobly_agent-1.3.0.dist-info → stoobly_agent-1.4.0.dist-info}/LICENSE +0 -0
- {stoobly_agent-1.3.0.dist-info → stoobly_agent-1.4.0.dist-info}/WHEEL +0 -0
- {stoobly_agent-1.3.0.dist-info → stoobly_agent-1.4.0.dist-info}/entry_points.txt +0 -0
stoobly_agent/__init__.py
CHANGED
@@ -1,2 +1,2 @@
|
|
1
1
|
COMMAND = 'stoobly-agent'
|
2
|
-
VERSION = '1.
|
2
|
+
VERSION = '1.4.0'
|
@@ -7,7 +7,7 @@ from mitmproxy.coretypes.multidict import MultiDict
|
|
7
7
|
from urllib.parse import urlparse, parse_qs
|
8
8
|
|
9
9
|
from stoobly_agent.app.proxy.replay.body_parser_service import decode_response
|
10
|
-
from stoobly_agent.config.constants import headers
|
10
|
+
from stoobly_agent.config.constants import custom_headers, headers
|
11
11
|
|
12
12
|
from .routes import ROUTES
|
13
13
|
from .simple_http_request_handler import SimpleHTTPRequestHandler
|
@@ -102,11 +102,11 @@ class ApplicationHTTPRequestHandler(SimpleHTTPRequestHandler):
|
|
102
102
|
'Content-Type',
|
103
103
|
headers.ACCESS_TOKEN.title(),
|
104
104
|
headers.CLIENT.title(),
|
105
|
-
|
105
|
+
custom_headers.DO_PROXY.title(),
|
106
106
|
headers.EXPIRY.title(),
|
107
107
|
headers.PROXY_HEADERS.title(),
|
108
108
|
headers.REQUEST_PATH.title(),
|
109
|
-
|
109
|
+
custom_headers.SERVICE_URL.title(),
|
110
110
|
headers.TOKEN_TYPE.title(),
|
111
111
|
headers.UID.title(),
|
112
112
|
])
|
@@ -3,7 +3,8 @@ import requests
|
|
3
3
|
from http.cookies import SimpleCookie
|
4
4
|
from urllib3.exceptions import InsecureRequestWarning
|
5
5
|
|
6
|
-
from stoobly_agent.
|
6
|
+
from stoobly_agent.app.api.simple_http_request_handler import SimpleHTTPRequestHandler
|
7
|
+
from stoobly_agent.config.constants import custom_headers, headers
|
7
8
|
from stoobly_agent.config.mitmproxy import MitmproxyConfig
|
8
9
|
from stoobly_agent.lib.logger import Logger
|
9
10
|
|
@@ -46,7 +47,7 @@ class ProxyController:
|
|
46
47
|
def do_PUT(self, context):
|
47
48
|
self.__proxy(context, requests.put)
|
48
49
|
|
49
|
-
def __proxy(self, context, method):
|
50
|
+
def __proxy(self, context: SimpleHTTPRequestHandler, method):
|
50
51
|
url = self.__get_url(context)
|
51
52
|
|
52
53
|
if url:
|
@@ -110,7 +111,7 @@ class ProxyController:
|
|
110
111
|
status = status,
|
111
112
|
)
|
112
113
|
|
113
|
-
def __get_headers(self, context):
|
114
|
+
def __get_headers(self, context: SimpleHTTPRequestHandler):
|
114
115
|
request_headers = dict(context.headers)
|
115
116
|
|
116
117
|
headers_white_list = []
|
@@ -125,12 +126,12 @@ class ProxyController:
|
|
125
126
|
|
126
127
|
return white_listed_headers
|
127
128
|
|
128
|
-
def __get_url(self, context):
|
129
|
-
service_url = context.headers.get(
|
129
|
+
def __get_url(self, context: SimpleHTTPRequestHandler):
|
130
|
+
service_url = context.headers.get(custom_headers.SERVICE_URL)
|
130
131
|
|
131
132
|
if not service_url:
|
132
133
|
context.render(
|
133
|
-
plain = f"Invalid {
|
134
|
+
plain = f"Invalid {custom_headers.SERVICE_URL} header {service_url}",
|
134
135
|
status = 400
|
135
136
|
)
|
136
137
|
return None
|
@@ -146,7 +147,7 @@ class ProxyController:
|
|
146
147
|
|
147
148
|
return f"{service_url}{request_path}"
|
148
149
|
|
149
|
-
def __get_cookies(self, context):
|
150
|
+
def __get_cookies(self, context: SimpleHTTPRequestHandler):
|
150
151
|
return SimpleCookie(context.headers.get('Cookie'))
|
151
152
|
|
152
153
|
def __get_body(self, context):
|
@@ -180,7 +180,7 @@ def rewrite(ctx):
|
|
180
180
|
@click.option('--project-key', help='Project to add rewrite rule to.')
|
181
181
|
@click.option(
|
182
182
|
'--type',
|
183
|
-
type=click.Choice([request_component.BODY_PARAM, request_component.HEADER, request_component.QUERY_PARAM]),
|
183
|
+
type=click.Choice([request_component.BODY_PARAM, request_component.HEADER, request_component.QUERY_PARAM, request_component.RESPONSE_HEADER, request_component.RESPONSE_PARAM]),
|
184
184
|
help='Request component type.'
|
185
185
|
)
|
186
186
|
@click.option(
|
@@ -94,6 +94,9 @@ class CertificateAuthority():
|
|
94
94
|
subprocess.run("sudo update-ca-trust extract".split(), check=True)
|
95
95
|
|
96
96
|
def install(self):
|
97
|
+
if not self.certs_generated:
|
98
|
+
self.generate_certs()
|
99
|
+
|
97
100
|
distro_name = distro.name(pretty=True)
|
98
101
|
|
99
102
|
# Ubuntu or other Debian based
|
@@ -105,7 +108,9 @@ class CertificateAuthority():
|
|
105
108
|
# elif distro.id() == 'rhel':
|
106
109
|
# return
|
107
110
|
else:
|
108
|
-
raise TypeError(
|
111
|
+
raise TypeError(
|
112
|
+
f"{distro_name} is not supported yet for automatic installation. For manual installation, see https://docs.mitmproxy.org/stable/concepts-certificates/"
|
113
|
+
)
|
109
114
|
|
110
115
|
def sign(self, hostname: str, dest = None, port = 443):
|
111
116
|
dest = dest or self.certs_dir
|
@@ -89,6 +89,23 @@ def print_scenarios(scenarios, **kwargs: TabulatePrintOptions):
|
|
89
89
|
select=kwargs.get('select') or []
|
90
90
|
)
|
91
91
|
|
92
|
+
def print_services(services, **kwargs: TabulatePrintOptions):
|
93
|
+
filter = []
|
94
|
+
format = kwargs.get('format')
|
95
|
+
|
96
|
+
if format == JSON_FORMAT:
|
97
|
+
json_print(services, **{
|
98
|
+
'filter': filter,
|
99
|
+
**kwargs
|
100
|
+
})
|
101
|
+
else:
|
102
|
+
tabulate_print(
|
103
|
+
services,
|
104
|
+
filter=filter,
|
105
|
+
headers=not kwargs.get('without_headers'),
|
106
|
+
select=kwargs.get('select') or []
|
107
|
+
)
|
108
|
+
|
92
109
|
def print_snapshots(snapshots, **kwargs: TabulatePrintOptions):
|
93
110
|
filter = ['resource_uuid']
|
94
111
|
format = kwargs.get('format')
|
@@ -54,8 +54,8 @@ class App():
|
|
54
54
|
return os.path.join(self.context_dir_path, DATA_DIR_NAME)
|
55
55
|
|
56
56
|
@property
|
57
|
-
def
|
58
|
-
return os.path.exists(self.
|
57
|
+
def valid(self):
|
58
|
+
return os.path.exists(self.scaffold_namespace_path)
|
59
59
|
|
60
60
|
@property
|
61
61
|
def scaffold_namespace(self):
|
@@ -18,8 +18,6 @@ FIXTURES_FOLDER_NAME = 'fixtures'
|
|
18
18
|
NAMESERVERS_FILE = '.nameservers'
|
19
19
|
SERVICE_DETACHED = '${SERVICE_DETACHED}'
|
20
20
|
SERVICE_DETACHED_ENV = 'SERVICE_DETACHED'
|
21
|
-
SERVICE_DOCKER_COMPOSE_PATH = '${SERVICE_DOCKER_COMPOSE_PATH}'
|
22
|
-
SERVICE_DOCKER_COMPOSE_PATH_ENV = 'SERVICE_DOCKER_COMPOSE_PATH'
|
23
21
|
SERVICE_HOSTNAME = '${SERVICE_HOSTNAME}'
|
24
22
|
SERVICE_HOSTNAME_ENV = 'SERVICE_HOSTNAME'
|
25
23
|
SERVICE_DNS = '${SERVICE_DNS}'
|
@@ -0,0 +1,112 @@
|
|
1
|
+
import os
|
2
|
+
import pdb
|
3
|
+
import sys
|
4
|
+
|
5
|
+
from dataclasses import dataclass
|
6
|
+
from typing import Union
|
7
|
+
|
8
|
+
SCAFFOLD_HOSTS_DELIMITTER_BEGIN = "##### STOOBLY SCAFFOLD HOSTS BEGIN #####\n"
|
9
|
+
SCAFFOLD_HOSTS_DELIMITTER_END = "##### STOOBLY SCAFFOLD HOSTS END #####\n"
|
10
|
+
|
11
|
+
class HostsFileManager():
|
12
|
+
|
13
|
+
@dataclass
|
14
|
+
class IpAddressToHostnames:
|
15
|
+
ip_address: str
|
16
|
+
hostnames: list[str]
|
17
|
+
|
18
|
+
def __get_hosts_file_path(self) -> str:
|
19
|
+
file_path = '/etc/hosts'
|
20
|
+
if not os.path.exists(file_path):
|
21
|
+
print(f"Error: File {file_path} not found.", file=sys.stderr)
|
22
|
+
sys.exit(1)
|
23
|
+
return file_path
|
24
|
+
|
25
|
+
# Split IP address and hostnames. Don't include inline comments
|
26
|
+
def __split_hosts_line(self, line: str) -> list[str]:
|
27
|
+
ip_addr_hosts_split = line.split('#')[0].split()
|
28
|
+
return ip_addr_hosts_split
|
29
|
+
|
30
|
+
# Parses hosts file and returns a mapping of IP address to hostnames in a list.
|
31
|
+
def get_hosts(self) -> list[IpAddressToHostnames]:
|
32
|
+
hosts_file_path = self.__get_hosts_file_path()
|
33
|
+
|
34
|
+
if not hosts_file_path:
|
35
|
+
return []
|
36
|
+
|
37
|
+
with open(hosts_file_path, 'r') as f:
|
38
|
+
hostlines = f.readlines()
|
39
|
+
|
40
|
+
# Skip comments and empty lines
|
41
|
+
hostlines = [line.strip() for line in hostlines
|
42
|
+
if not line.startswith('#') and line.strip() != '']
|
43
|
+
|
44
|
+
hosts = []
|
45
|
+
for line in hostlines:
|
46
|
+
ip_addr_hosts_split = self.__split_hosts_line(line)
|
47
|
+
ip_address = ip_addr_hosts_split[0]
|
48
|
+
hostnames = ip_addr_hosts_split[1:]
|
49
|
+
ipAddressToHostnames = self.IpAddressToHostnames(ip_address, hostnames)
|
50
|
+
|
51
|
+
hosts.append(ipAddressToHostnames)
|
52
|
+
|
53
|
+
return hosts
|
54
|
+
|
55
|
+
def find_host(self, hostname) -> Union[IpAddressToHostnames, None]:
|
56
|
+
hosts = self.get_hosts()
|
57
|
+
|
58
|
+
for mapping in hosts:
|
59
|
+
if ((mapping.ip_address == '0.0.0.0' or mapping.ip_address == '127.0.0.1') and
|
60
|
+
hostname in mapping.hostnames):
|
61
|
+
|
62
|
+
return mapping
|
63
|
+
|
64
|
+
return None
|
65
|
+
|
66
|
+
def install_hostnames(self, hostnames: list[str]) -> None:
|
67
|
+
hosts_file_path = self.__get_hosts_file_path()
|
68
|
+
|
69
|
+
self.remove_lines_between_markers(
|
70
|
+
hosts_file_path, SCAFFOLD_HOSTS_DELIMITTER_BEGIN, SCAFFOLD_HOSTS_DELIMITTER_END
|
71
|
+
)
|
72
|
+
|
73
|
+
with open(hosts_file_path, 'a+') as f:
|
74
|
+
if SCAFFOLD_HOSTS_DELIMITTER_BEGIN not in f.read():
|
75
|
+
f.write(SCAFFOLD_HOSTS_DELIMITTER_BEGIN)
|
76
|
+
|
77
|
+
for hostname in hostnames:
|
78
|
+
print(f"Installing hostname {hostname} to {hosts_file_path}")
|
79
|
+
f.write(f"127.0.0.1 {hostname}\n")
|
80
|
+
f.write(f"::1 {hostname}\n")
|
81
|
+
|
82
|
+
if SCAFFOLD_HOSTS_DELIMITTER_END not in f.read():
|
83
|
+
f.write(SCAFFOLD_HOSTS_DELIMITTER_END)
|
84
|
+
|
85
|
+
def uninstall_hostnames(self) -> None:
|
86
|
+
hosts_file_path = self.__get_hosts_file_path()
|
87
|
+
|
88
|
+
self.remove_lines_between_markers(
|
89
|
+
hosts_file_path, SCAFFOLD_HOSTS_DELIMITTER_BEGIN, SCAFFOLD_HOSTS_DELIMITTER_END
|
90
|
+
)
|
91
|
+
|
92
|
+
print(f"Uninstalled hostnames from {hosts_file_path}")
|
93
|
+
|
94
|
+
def remove_lines_between_markers(self, file_path, start_marker, end_marker):
|
95
|
+
with open(file_path, "r") as file:
|
96
|
+
lines = file.readlines()
|
97
|
+
|
98
|
+
inside_block = False
|
99
|
+
filtered_lines = []
|
100
|
+
|
101
|
+
for line in lines:
|
102
|
+
if start_marker in line:
|
103
|
+
inside_block = True
|
104
|
+
continue # Skip the start marker line
|
105
|
+
if end_marker in line:
|
106
|
+
inside_block = False
|
107
|
+
continue # Skip the end marker line
|
108
|
+
if not inside_block:
|
109
|
+
filtered_lines.append(line)
|
110
|
+
|
111
|
+
with open(file_path, "w") as file:
|
112
|
+
file.writelines(filtered_lines)
|
@@ -4,7 +4,6 @@ import pdb
|
|
4
4
|
from .config import Config
|
5
5
|
from .constants import (
|
6
6
|
SERVICE_DETACHED_ENV,
|
7
|
-
SERVICE_DOCKER_COMPOSE_PATH_ENV,
|
8
7
|
SERVICE_HOSTNAME_ENV,
|
9
8
|
SERVICE_PRIORITY_ENV,
|
10
9
|
SERVICE_PORT_ENV,
|
@@ -18,7 +17,6 @@ class ServiceConfig(Config):
|
|
18
17
|
super().__init__(dir)
|
19
18
|
|
20
19
|
self.__detached = None
|
21
|
-
self.__docker_compose_path = None
|
22
20
|
self.__hostname = None
|
23
21
|
self.__port = None
|
24
22
|
self.__priority = None
|
@@ -53,14 +51,6 @@ class ServiceConfig(Config):
|
|
53
51
|
def detached(self, v):
|
54
52
|
self.__detached = v
|
55
53
|
|
56
|
-
@property
|
57
|
-
def docker_compose_path(self):
|
58
|
-
return self.__docker_compose_path
|
59
|
-
|
60
|
-
@docker_compose_path.setter
|
61
|
-
def docker_compose_path(self, v):
|
62
|
-
self.__docker_compose_path = v
|
63
|
-
|
64
54
|
@property
|
65
55
|
def hostname(self):
|
66
56
|
return (self.__hostname or '').strip()
|
@@ -120,19 +110,25 @@ class ServiceConfig(Config):
|
|
120
110
|
config = config or self.read()
|
121
111
|
|
122
112
|
self.detached = config.get(SERVICE_DETACHED_ENV)
|
123
|
-
self.docker_compose_path = config.get(SERVICE_DOCKER_COMPOSE_PATH_ENV)
|
124
113
|
self.hostname = config.get(SERVICE_HOSTNAME_ENV)
|
125
114
|
self.port = config.get(SERVICE_PORT_ENV)
|
126
115
|
self.priority = config.get(SERVICE_PRIORITY_ENV)
|
127
116
|
self.proxy_mode = config.get(SERVICE_PROXY_MODE_ENV)
|
128
117
|
self.scheme = config.get(SERVICE_SCHEME_ENV)
|
129
118
|
|
119
|
+
def to_dict(self):
|
120
|
+
return {
|
121
|
+
'detached': self.detached,
|
122
|
+
'hostname': self.hostname,
|
123
|
+
'port': self.port,
|
124
|
+
'priority': self.priority,
|
125
|
+
'proxy_mode': self.proxy_mode,
|
126
|
+
'scheme': self.scheme,
|
127
|
+
}
|
128
|
+
|
130
129
|
def write(self):
|
131
130
|
config = {}
|
132
131
|
|
133
|
-
if self.docker_compose_path:
|
134
|
-
config[SERVICE_DOCKER_COMPOSE_PATH_ENV] = self.docker_compose_path
|
135
|
-
|
136
132
|
if self.hostname:
|
137
133
|
config[SERVICE_HOSTNAME_ENV] = self.hostname
|
138
134
|
|
@@ -18,7 +18,7 @@ from stoobly_agent.app.cli.scaffold.constants import (
|
|
18
18
|
WORKFLOW_RECORD_TYPE,
|
19
19
|
WORKFLOW_TEST_TYPE,
|
20
20
|
)
|
21
|
-
from stoobly_agent.app.cli.scaffold.
|
21
|
+
from stoobly_agent.app.cli.scaffold.hosts_file_manager import HostsFileManager
|
22
22
|
from stoobly_agent.app.cli.scaffold.service_command import ServiceCommand
|
23
23
|
from stoobly_agent.app.cli.scaffold.service_docker_compose import ServiceDockerCompose
|
24
24
|
from stoobly_agent.app.cli.scaffold.validate_command import ValidateCommand
|
@@ -89,8 +89,8 @@ class ServiceWorkflowValidateCommand(ServiceCommand, ValidateCommand):
|
|
89
89
|
def hostname_exists(self, hostname: str) -> bool:
|
90
90
|
print(f"Validating hostname exists in hosts file for hostname: {hostname}")
|
91
91
|
|
92
|
-
|
93
|
-
host_mapping =
|
92
|
+
hosts_file_manager = HostsFileManager()
|
93
|
+
host_mapping = hosts_file_manager.find_host(hostname)
|
94
94
|
if host_mapping:
|
95
95
|
print(f"Correct hosts mapping found for {hostname}")
|
96
96
|
return True
|
@@ -4,7 +4,7 @@
|
|
4
4
|
# STOOBLY_CA_CERTS_DIR: path to folder where ca certs are stored
|
5
5
|
# STOOBLY_CERTS_DIR: path to a folder to store certs
|
6
6
|
# STOOBLY_CONTEXT_DIR: path to the folder containing the .stoobly folder
|
7
|
-
#
|
7
|
+
# STOOBLY_WORKFLOW_SERVICE_OPTIONS: extra --service options to pass 'stoobly-agent scaffold workflow' commands
|
8
8
|
|
9
9
|
# Constants
|
10
10
|
DIR := $(dir $(realpath $(lastword $(MAKEFILE_LIST))))
|
@@ -24,7 +24,7 @@ context_dir_option=--context-dir-path $(context_dir)
|
|
24
24
|
user_id_option=--user-id $(USER_ID)
|
25
25
|
stoobly_exec_options=--profile $(EXEC_WORKFLOW_NAME) -p $(EXEC_WORKFLOW_NAME)
|
26
26
|
workflow_down_options=$(user_id_option)
|
27
|
-
|
27
|
+
workflow_service_options=$(shell echo $$STOOBLY_WORKFLOW_SERVICE_OPTIONS)
|
28
28
|
workflow_up_options=$(context_dir_option) --ca-certs-dir-path $(ca_certs_dir) --certs-dir-path $(certs_dir) --from-make $(user_id_option)
|
29
29
|
|
30
30
|
app_data_dir=$(app_dir)/.stoobly
|
@@ -60,9 +60,16 @@ stoobly_exec_run_env=$(source_env) && $(exec_env) && export CONTEXT_DIR="$(app_d
|
|
60
60
|
# Workflow run
|
61
61
|
workflow_run=$(source_env) && bash "$(workflow_run_script)"
|
62
62
|
|
63
|
+
ca-cert/install: stoobly/install
|
64
|
+
@echo "Running stoobly-agent ca-cert install..."; \
|
65
|
+
stoobly-agent ca-cert install
|
63
66
|
certs:
|
64
67
|
@export EXEC_COMMAND=bin/.mkcert && \
|
65
68
|
$(stoobly_exec)
|
69
|
+
command/install:
|
70
|
+
$(eval COMMAND=install)
|
71
|
+
command/uninstall:
|
72
|
+
$(eval COMMAND=uninstall)
|
66
73
|
nameservers: tmpdir
|
67
74
|
@if [ -f /etc/resolv.conf ]; then \
|
68
75
|
nameserver=$$(grep -Eo '([0-9]{1,3}\.){3}[0-9]{1,3}' /etc/resolv.conf) && \
|
@@ -72,7 +79,8 @@ nameservers: tmpdir
|
|
72
79
|
fi; \
|
73
80
|
echo "$$nameserver" > $(app_tmp_dir)/.nameservers; \
|
74
81
|
else \
|
75
|
-
echo "/etc/resolv.conf not found."; \
|
82
|
+
echo "/etc/resolv.conf not found." >&2; \
|
83
|
+
exit 1; \
|
76
84
|
fi
|
77
85
|
intercept/disable:
|
78
86
|
@export EXEC_COMMAND=bin/.disable && \
|
@@ -81,42 +89,24 @@ intercept/enable:
|
|
81
89
|
@export EXEC_COMMAND=bin/.enable && \
|
82
90
|
export EXEC_ARGS=$(scenario_key) && \
|
83
91
|
$(stoobly_exec)
|
84
|
-
mock: nameservers
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
record: nameservers
|
103
|
-
@export EXEC_COMMAND=bin/.up && \
|
104
|
-
export EXEC_OPTIONS="$(workflow_up_options) $(workflow_options)$(options)" && \
|
105
|
-
export EXEC_ARGS="record" && \
|
106
|
-
$(stoobly_exec_run) && \
|
107
|
-
$(workflow_run)
|
108
|
-
record/logs:
|
109
|
-
@export EXEC_COMMAND=bin/.logs && \
|
110
|
-
export EXEC_OPTIONS="$(workflow_options)$(options)" && \
|
111
|
-
export EXEC_ARGS="record" && \
|
112
|
-
$(stoobly_exec_run) && \
|
113
|
-
$(workflow_run)
|
114
|
-
record/down:
|
115
|
-
@export EXEC_COMMAND=bin/.down && \
|
116
|
-
export EXEC_OPTIONS="$(workflow_down_options) $(workflow_options)$(options)" && \
|
117
|
-
export EXEC_ARGS="record" && \
|
118
|
-
$(stoobly_exec_run) && \
|
119
|
-
$(workflow_run)
|
92
|
+
mock: workflow/mock workflow/hostname/install nameservers workflow/up
|
93
|
+
mock/services: workflow/mock workflow/services
|
94
|
+
mock/logs: workflow/mock workflow/logs
|
95
|
+
mock/down: workflow/mock workflow/down workflow/hostname/uninstall
|
96
|
+
pipx/install:
|
97
|
+
@if ! command -v pipx >/dev/null 2>&1; then \
|
98
|
+
echo "pipx is not installed. Installing pipx..."; \
|
99
|
+
python3 -m pip install --user pipx && python3 -m pipx ensurepath; \
|
100
|
+
fi
|
101
|
+
python/validate:
|
102
|
+
@if ! python3 --version | grep -Eq 'Python 3\.(10|11|12)'; then \
|
103
|
+
echo "Error: Python 3.10, 3.11, or 3.12 is required."; \
|
104
|
+
exit 1; \
|
105
|
+
fi
|
106
|
+
record: workflow/record workflow/hostname/install nameservers workflow/up
|
107
|
+
record/down: workflow/record workflow/down workflow/hostname/uninstall
|
108
|
+
record/services: workflow/record workflow/services
|
109
|
+
record/logs: workflow/record workflow/logs
|
120
110
|
scenario/create:
|
121
111
|
# Create a scenario
|
122
112
|
@export EXEC_COMMAND=bin/.create && \
|
@@ -146,23 +136,57 @@ scenario/snapshot:
|
|
146
136
|
export EXEC_OPTIONS="$(options)" && \
|
147
137
|
export EXEC_ARGS="$(key)" && \
|
148
138
|
$(stoobly_exec)
|
149
|
-
|
150
|
-
@
|
151
|
-
|
152
|
-
|
139
|
+
stoobly/install: python/validate pipx/install
|
140
|
+
@if ! pipx list 2> /dev/null | grep -q 'stoobly-agent'; then \
|
141
|
+
echo "stoobly-agent not found. Installing..."; \
|
142
|
+
pipx install stoobly-agent; \
|
143
|
+
fi
|
144
|
+
test: workflow/test workflow/up
|
145
|
+
test/services: workflow/test workflow/services
|
146
|
+
test/logs: workflow/test workflow/logs
|
147
|
+
test/down: workflow/test workflow/down
|
148
|
+
tmpdir:
|
149
|
+
@mkdir -p $(app_tmp_dir)
|
150
|
+
workflow/down:
|
151
|
+
@export EXEC_COMMAND=bin/.down && \
|
152
|
+
export EXEC_OPTIONS="$(workflow_down_options) $(workflow_service_options) $(options)" && \
|
153
|
+
export EXEC_ARGS="$(WORKFLOW)" && \
|
153
154
|
$(stoobly_exec_run) && \
|
154
155
|
$(workflow_run)
|
155
|
-
|
156
|
+
workflow/hostname: stoobly/install
|
157
|
+
@read -p "Do you want to $(COMMAND) hostname(s) in /etc/hosts? (y/N) " confirm && \
|
158
|
+
if [ "$$confirm" = "y" ] || [ "$$confirm" = "Y" ]; then \
|
159
|
+
CURRENT_VERSION=$$(stoobly-agent --version); \
|
160
|
+
REQUIRED_VERSION="1.4.0"; \
|
161
|
+
if [ "$$(printf '%s\n' "$$REQUIRED_VERSION" "$$CURRENT_VERSION" | sort -V | tail -n 1)" = "$$CURRENT_VERSION" ]; then \
|
162
|
+
echo "stoobly-agent version $$REQUIRED_VERSION required. Please run: pipx upgrade stoobly-agent"; \
|
163
|
+
exit 1; \
|
164
|
+
fi; \
|
165
|
+
echo "Running stoobly-agent scaffold hostname $(COMMAND)..."; \
|
166
|
+
stoobly-agent scaffold hostname $(COMMAND) --app-dir-path $(app_dir) --workflow $(WORKFLOW); \
|
167
|
+
fi
|
168
|
+
workflow/hostname/install: command/install workflow/hostname
|
169
|
+
workflow/hostname/uninstall: command/uninstall workflow/hostname
|
170
|
+
workflow/logs:
|
156
171
|
@export EXEC_COMMAND=bin/.logs && \
|
157
|
-
export EXEC_OPTIONS="$(
|
158
|
-
export EXEC_ARGS="
|
172
|
+
export EXEC_OPTIONS="$(workflow_service_options) $(options)" && \
|
173
|
+
export EXEC_ARGS="$(WORKFLOW)" && \
|
159
174
|
$(stoobly_exec_run) && \
|
160
175
|
$(workflow_run)
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
176
|
+
workflow/mock:
|
177
|
+
$(eval WORKFLOW=mock)
|
178
|
+
workflow/record:
|
179
|
+
$(eval WORKFLOW=record)
|
180
|
+
workflow/services:
|
181
|
+
@export EXEC_COMMAND=bin/.services && \
|
182
|
+
export EXEC_OPTIONS="$(workflow_service_options) $(options)" && \
|
183
|
+
export EXEC_ARGS="$(WORKFLOW)" && \
|
184
|
+
$(stoobly_exec_run)
|
185
|
+
workflow/test:
|
186
|
+
$(eval WORKFLOW=test)
|
187
|
+
workflow/up:
|
188
|
+
@export EXEC_COMMAND=bin/.up && \
|
189
|
+
export EXEC_OPTIONS="$(workflow_up_options) $(workflow_service_options) $(options)" && \
|
190
|
+
export EXEC_ARGS="$(WORKFLOW)" && \
|
165
191
|
$(stoobly_exec_run) && \
|
166
|
-
$(workflow_run)
|
167
|
-
tmpdir:
|
168
|
-
@mkdir -p $(app_tmp_dir)
|
192
|
+
$(workflow_run)
|
@@ -17,7 +17,6 @@ CORE_WORKFLOWS = [WORKFLOW_MOCK_TYPE, WORKFLOW_RECORD_TYPE, WORKFLOW_TEST_TYPE]
|
|
17
17
|
|
18
18
|
class BuildOptions(TypedDict):
|
19
19
|
builder_class: type
|
20
|
-
headless: bool
|
21
20
|
service_builder: ServiceBuilder
|
22
21
|
template: WORKFLOW_TEMPLATE
|
23
22
|
workflow_decorators: List[Union[MockDecorator, ReverseProxyDecorator]]
|
@@ -29,10 +29,12 @@ class BuildOptions(ComposeOptions):
|
|
29
29
|
verbose: bool
|
30
30
|
|
31
31
|
class DownOptions(ComposeOptions):
|
32
|
+
extra_compose_path: str
|
32
33
|
rmi: bool
|
33
34
|
|
34
35
|
class UpOptions(ComposeOptions):
|
35
36
|
attached: bool
|
37
|
+
extra_compose_path: str
|
36
38
|
pull: bool
|
37
39
|
|
38
40
|
class WorkflowRunCommand(WorkflowCommand):
|
@@ -43,7 +45,6 @@ class WorkflowRunCommand(WorkflowCommand):
|
|
43
45
|
self.__ca_certs_dir_path = kwargs.get('ca_certs_dir_path') or app.ca_certs_dir_path
|
44
46
|
self.__certs_dir_path = kwargs.get('certs_dir_path') or app.certs_dir_path
|
45
47
|
self.__context_dir_path = kwargs.get('context_dir_path') or app.context_dir_path
|
46
|
-
self.__extra_compose_path = kwargs.get('extra_compose_path')
|
47
48
|
self.__network = kwargs.get('network') or self.app_config.network
|
48
49
|
|
49
50
|
@property
|
@@ -70,10 +71,6 @@ class WorkflowRunCommand(WorkflowCommand):
|
|
70
71
|
def current_working_dir(self, v):
|
71
72
|
self.__current_working_dir = v
|
72
73
|
|
73
|
-
@property
|
74
|
-
def extra_compose_path(self):
|
75
|
-
return self.__extra_compose_path
|
76
|
-
|
77
74
|
@property
|
78
75
|
def nameservers(self):
|
79
76
|
path = self.nameservers_path
|
@@ -153,8 +150,8 @@ class WorkflowRunCommand(WorkflowCommand):
|
|
153
150
|
|
154
151
|
command_options.append(f"-f {os.path.relpath(self.custom_compose_path, self.__current_working_dir)}")
|
155
152
|
|
156
|
-
if
|
157
|
-
command_options.append(f"-f {os.path.relpath(
|
153
|
+
if options.get('extra_compose_path'):
|
154
|
+
command_options.append(f"-f {os.path.relpath(options['extra_compose_path'], self.__current_working_dir)}")
|
158
155
|
|
159
156
|
command_options.append(f"--profile {self.workflow_name}")
|
160
157
|
|
@@ -203,8 +200,8 @@ class WorkflowRunCommand(WorkflowCommand):
|
|
203
200
|
if self.custom_services:
|
204
201
|
command.append(f"-f {os.path.relpath(self.custom_compose_path, self.__current_working_dir)}")
|
205
202
|
|
206
|
-
if
|
207
|
-
command.append(f"-f {os.path.relpath(
|
203
|
+
if options.get('extra_compose_path'):
|
204
|
+
command.append(f"-f {os.path.relpath(options['extra_compose_path'], self.__current_working_dir)}")
|
208
205
|
|
209
206
|
command.append(f"--profile {self.workflow_name}")
|
210
207
|
|