stoobly-agent 1.2.3__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.
Files changed (102) hide show
  1. stoobly_agent/__init__.py +1 -1
  2. stoobly_agent/app/api/application_http_request_handler.py +3 -3
  3. stoobly_agent/app/api/proxy_controller.py +8 -7
  4. stoobly_agent/app/cli/config_cli.py +1 -1
  5. stoobly_agent/app/cli/helpers/certificate_authority.py +7 -6
  6. stoobly_agent/app/cli/helpers/print_service.py +17 -0
  7. stoobly_agent/app/cli/scaffold/app.py +16 -34
  8. stoobly_agent/app/cli/scaffold/app_command.py +4 -7
  9. stoobly_agent/app/cli/scaffold/app_config.py +15 -2
  10. stoobly_agent/app/cli/scaffold/app_create_command.py +18 -2
  11. stoobly_agent/app/cli/scaffold/command.py +1 -1
  12. stoobly_agent/app/cli/scaffold/constants.py +9 -5
  13. stoobly_agent/app/cli/scaffold/docker/app_builder.py +3 -7
  14. stoobly_agent/app/cli/scaffold/docker/constants.py +0 -1
  15. stoobly_agent/app/cli/scaffold/docker/service/builder.py +12 -11
  16. stoobly_agent/app/cli/scaffold/docker/workflow/builder.py +14 -31
  17. stoobly_agent/app/cli/scaffold/docker/workflow/mock_decorator.py +6 -2
  18. stoobly_agent/app/cli/scaffold/docker/workflow/reverse_proxy_decorator.py +6 -2
  19. stoobly_agent/app/cli/scaffold/hosts_file_manager.py +112 -0
  20. stoobly_agent/app/cli/scaffold/service.py +1 -2
  21. stoobly_agent/app/cli/scaffold/service_command.py +1 -1
  22. stoobly_agent/app/cli/scaffold/service_config.py +10 -14
  23. stoobly_agent/app/cli/scaffold/service_workflow_validate_command.py +9 -11
  24. stoobly_agent/app/cli/scaffold/templates/app/.Dockerfile.context +2 -4
  25. stoobly_agent/app/cli/scaffold/templates/app/.Makefile +108 -68
  26. stoobly_agent/app/cli/scaffold/templates/app/.docker-compose.base.yml +8 -13
  27. stoobly_agent/app/cli/scaffold/templates/app/Makefile +1 -1
  28. stoobly_agent/app/cli/scaffold/templates/app/build/.docker-compose.base.yml +8 -4
  29. stoobly_agent/app/cli/scaffold/templates/app/build/mock/.docker-compose.mock.yml +2 -6
  30. stoobly_agent/app/cli/scaffold/templates/app/build/mock/bin/.configure +3 -0
  31. stoobly_agent/app/cli/scaffold/templates/app/build/mock/bin/.init +3 -0
  32. stoobly_agent/app/cli/scaffold/templates/app/build/record/.docker-compose.record.yml +2 -6
  33. stoobly_agent/app/cli/scaffold/templates/app/build/record/bin/.configure +3 -0
  34. stoobly_agent/app/cli/scaffold/templates/app/build/record/bin/.init +3 -0
  35. stoobly_agent/app/cli/scaffold/templates/app/build/test/.docker-compose.test.yml +2 -6
  36. stoobly_agent/app/cli/scaffold/templates/app/build/test/bin/.configure +3 -0
  37. stoobly_agent/app/cli/scaffold/templates/app/build/test/bin/.init +3 -0
  38. stoobly_agent/app/cli/scaffold/templates/app/entrypoint/.docker-compose.base.yml +2 -0
  39. stoobly_agent/app/cli/scaffold/templates/app/entrypoint/mock/.docker-compose.mock.yml +2 -8
  40. stoobly_agent/app/cli/scaffold/templates/app/entrypoint/record/.docker-compose.record.yml +2 -8
  41. stoobly_agent/app/cli/scaffold/templates/app/entrypoint/test/.docker-compose.test.yml +2 -8
  42. stoobly_agent/app/cli/scaffold/templates/app/stoobly-ui/exec/.docker-compose.exec.yml +2 -3
  43. stoobly_agent/app/cli/scaffold/templates/app/stoobly-ui/exec/bin/.logs +1 -0
  44. stoobly_agent/app/cli/scaffold/templates/app/stoobly-ui/exec/bin/.services +9 -0
  45. stoobly_agent/app/cli/scaffold/templates/app/stoobly-ui/mock/.docker-compose.mock.yml +1 -2
  46. stoobly_agent/app/cli/scaffold/templates/app/stoobly-ui/record/.docker-compose.record.yml +1 -2
  47. stoobly_agent/app/cli/scaffold/templates/workflow/mock/bin/.configure +3 -0
  48. stoobly_agent/app/cli/scaffold/templates/workflow/mock/bin/.init +7 -1
  49. stoobly_agent/app/cli/scaffold/templates/workflow/record/bin/.configure +3 -0
  50. stoobly_agent/app/cli/scaffold/templates/workflow/record/bin/.init +7 -1
  51. stoobly_agent/app/cli/scaffold/templates/workflow/test/bin/.configure +3 -0
  52. stoobly_agent/app/cli/scaffold/templates/workflow/test/bin/.init +7 -1
  53. stoobly_agent/app/cli/scaffold/validate_command.py +2 -2
  54. stoobly_agent/app/cli/scaffold/workflow.py +5 -4
  55. stoobly_agent/app/cli/scaffold/workflow_command.py +3 -3
  56. stoobly_agent/app/cli/scaffold/workflow_create_command.py +0 -1
  57. stoobly_agent/app/cli/scaffold/workflow_run_command.py +78 -45
  58. stoobly_agent/app/cli/scaffold_cli.py +246 -109
  59. stoobly_agent/app/cli/snapshot_cli.py +7 -3
  60. stoobly_agent/app/models/adapters/joined_request_adapter.py +6 -0
  61. stoobly_agent/app/models/factories/resource/local_db/helpers/scenario_snapshot.py +3 -1
  62. stoobly_agent/app/models/helpers/apply.py +34 -17
  63. stoobly_agent/app/models/helpers/create_request_params_service.py +4 -0
  64. stoobly_agent/app/proxy/handle_mock_service.py +2 -0
  65. stoobly_agent/app/proxy/handle_replay_service.py +2 -0
  66. stoobly_agent/app/proxy/mitmproxy/request_facade.py +1 -1
  67. stoobly_agent/app/proxy/mitmproxy/response_body_facade.py +19 -0
  68. stoobly_agent/app/proxy/mitmproxy/response_facade.py +90 -18
  69. stoobly_agent/app/proxy/record/join_request_service.py +1 -1
  70. stoobly_agent/app/proxy/replay/body_parser_service.py +11 -3
  71. stoobly_agent/app/settings/constants/request_component.py +2 -1
  72. stoobly_agent/config/constants/custom_headers.py +13 -13
  73. stoobly_agent/config/constants/headers.py +0 -2
  74. stoobly_agent/config/data_dir.py +2 -1
  75. stoobly_agent/config/schema.yml +2 -2
  76. stoobly_agent/public/18-es2015.583f191cc7ad512ee262.js +1 -0
  77. stoobly_agent/public/18-es5.583f191cc7ad512ee262.js +1 -0
  78. stoobly_agent/public/35-es2015.8f79ff8748d4ff06ab03.js +1 -0
  79. stoobly_agent/public/35-es5.8f79ff8748d4ff06ab03.js +1 -0
  80. stoobly_agent/public/index.html +1 -1
  81. stoobly_agent/public/main-es2015.2cc16523aa3fcaba51e5.js +1 -0
  82. stoobly_agent/public/main-es5.2cc16523aa3fcaba51e5.js +1 -0
  83. stoobly_agent/public/{runtime-es2015.9addf49b79aca951b7e2.js → runtime-es2015.b914470164e4d6e75d96.js} +1 -1
  84. stoobly_agent/public/{runtime-es5.9addf49b79aca951b7e2.js → runtime-es5.b914470164e4d6e75d96.js} +1 -1
  85. stoobly_agent/test/app/cli/scaffold/cli_invoker.py +1 -2
  86. stoobly_agent/test/app/cli/scaffold/{hosts_file_reader_test.py → hosts_file_manager_test.py} +20 -20
  87. stoobly_agent/test/app/cli/snapshot/snapshot_apply_test.py +162 -1
  88. stoobly_agent/test/app/models/schemas/.stoobly/db/VERSION +1 -1
  89. stoobly_agent/test/mock_data/scaffold/docker-compose-assets-service.yml +1 -3
  90. {stoobly_agent-1.2.3.dist-info → stoobly_agent-1.4.0.dist-info}/METADATA +1 -1
  91. {stoobly_agent-1.2.3.dist-info → stoobly_agent-1.4.0.dist-info}/RECORD +94 -93
  92. stoobly_agent/app/cli/scaffold/hosts_file_reader.py +0 -65
  93. stoobly_agent/app/cli/scaffold/templates/app/.Dockerfile.proxy +0 -34
  94. stoobly_agent/public/18-es2015.d3b430636a4d6f544d92.js +0 -1
  95. stoobly_agent/public/18-es5.d3b430636a4d6f544d92.js +0 -1
  96. stoobly_agent/public/35-es2015.f741ebce0bfc25f0ec99.js +0 -1
  97. stoobly_agent/public/35-es5.f741ebce0bfc25f0ec99.js +0 -1
  98. stoobly_agent/public/main-es2015.ccd46ac1b6638ddf2066.js +0 -1
  99. stoobly_agent/public/main-es5.ccd46ac1b6638ddf2066.js +0 -1
  100. {stoobly_agent-1.2.3.dist-info → stoobly_agent-1.4.0.dist-info}/LICENSE +0 -0
  101. {stoobly_agent-1.2.3.dist-info → stoobly_agent-1.4.0.dist-info}/WHEEL +0 -0
  102. {stoobly_agent-1.2.3.dist-info → stoobly_agent-1.4.0.dist-info}/entry_points.txt +0 -0
@@ -1,8 +1,9 @@
1
+ import os
1
2
  import pdb
2
3
 
3
4
  from urllib.parse import urlparse
4
5
 
5
- from ...constants import SERVICE_HOSTNAME, SERVICE_PORT
6
+ from ...constants import SERVICE_HOSTNAME, SERVICE_PORT, STOOBLY_CERTS_DIR
6
7
  from .builder import WorkflowBuilder
7
8
 
8
9
  class ReverseProxyDecorator():
@@ -22,7 +23,6 @@ class ReverseProxyDecorator():
22
23
  config = self.service_builder.config
23
24
 
24
25
  command = [
25
- '--certs', f"/etc/ssl/certs/{SERVICE_HOSTNAME}-joined.pem",
26
26
  '--headless',
27
27
  '--lifecycle-hooks-path', 'lifecycle_hooks.py',
28
28
  '--proxy-mode', config.proxy_mode,
@@ -30,6 +30,10 @@ class ReverseProxyDecorator():
30
30
  '--ssl-insecure'
31
31
  ]
32
32
 
33
+ if config.scheme == 'https':
34
+ command.append('--certs')
35
+ command.append(os.path.join(STOOBLY_CERTS_DIR, f"{SERVICE_HOSTNAME}-joined.pem"))
36
+
33
37
  services = self.workflow_builder.services
34
38
  proxy_name = self.workflow_builder.proxy
35
39
  proxy_service = services.get(proxy_name) or {}
@@ -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)
@@ -14,12 +14,11 @@ class Service():
14
14
 
15
15
  @property
16
16
  def dir_path(self):
17
- return os.path.join(self.app.namespace_path, self.service_name)
17
+ return os.path.join(self.app.scaffold_namespace_path, self.service_name)
18
18
 
19
19
  @property
20
20
  def service_name(self):
21
21
  return self.__service_name
22
22
 
23
- @property
24
23
  def workflow_dir_path(self, workflow_name: str):
25
24
  return os.path.join(self.dir_path, workflow_name)
@@ -33,7 +33,7 @@ class ServiceCommand(AppCommand):
33
33
  @property
34
34
  def service_path(self):
35
35
  return os.path.join(
36
- self.scaffold_dir_path,
36
+ self.data_dir_path,
37
37
  self.service_relative_path
38
38
  )
39
39
 
@@ -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
 
@@ -1,28 +1,24 @@
1
1
  import os
2
2
  import pdb
3
3
  import socket
4
- import ssl
5
4
  import time
5
+
6
6
  from collections import Counter
7
7
  from pathlib import Path
8
8
 
9
- import requests
10
9
  import yaml
11
10
  from docker.models.containers import Container
12
- from requests.adapters import HTTPAdapter
13
- from urllib3 import Retry
14
11
 
15
12
  from stoobly_agent.app.cli.scaffold.constants import (
16
13
  FIXTURES_FOLDER_NAME,
17
14
  STOOBLY_DATA_DIR,
18
- STOOBLY_HOME_DIR,
19
15
  VIRTUAL_HOST_ENV,
20
16
  VIRTUAL_PORT_ENV,
21
17
  VIRTUAL_PROTO_ENV,
22
18
  WORKFLOW_RECORD_TYPE,
23
19
  WORKFLOW_TEST_TYPE,
24
20
  )
25
- from stoobly_agent.app.cli.scaffold.hosts_file_reader import HostsFileReader
21
+ from stoobly_agent.app.cli.scaffold.hosts_file_manager import HostsFileManager
26
22
  from stoobly_agent.app.cli.scaffold.service_command import ServiceCommand
27
23
  from stoobly_agent.app.cli.scaffold.service_docker_compose import ServiceDockerCompose
28
24
  from stoobly_agent.app.cli.scaffold.validate_command import ValidateCommand
@@ -39,7 +35,9 @@ class ServiceWorkflowValidateCommand(ServiceCommand, ValidateCommand):
39
35
 
40
36
  self.workflow_name = kwargs['workflow_name']
41
37
  self.hostname = self.service_config.hostname
42
- self.service_docker_compose = ServiceDockerCompose(app_dir_path=app.dir_path, target_workflow_name=self.workflow_name, service_name=self.service_name, hostname=self.hostname)
38
+ self.service_docker_compose = ServiceDockerCompose(
39
+ app_dir_path=app.dir_path, target_workflow_name=self.workflow_name, service_name=self.service_name, hostname=self.hostname
40
+ )
43
41
 
44
42
  @property
45
43
  def fixtures_dir_path(self):
@@ -48,7 +46,7 @@ class ServiceWorkflowValidateCommand(ServiceCommand, ValidateCommand):
48
46
  @property
49
47
  def workflow_path(self):
50
48
  return os.path.join(
51
- self.scaffold_dir_path,
49
+ self.data_dir_path,
52
50
  self.workflow_relative_path
53
51
  )
54
52
  @property
@@ -91,8 +89,8 @@ class ServiceWorkflowValidateCommand(ServiceCommand, ValidateCommand):
91
89
  def hostname_exists(self, hostname: str) -> bool:
92
90
  print(f"Validating hostname exists in hosts file for hostname: {hostname}")
93
91
 
94
- hosts_file_reader = HostsFileReader()
95
- host_mapping = hosts_file_reader.find_host(hostname)
92
+ hosts_file_manager = HostsFileManager()
93
+ host_mapping = hosts_file_manager.find_host(hostname)
96
94
  if host_mapping:
97
95
  print(f"Correct hosts mapping found for {hostname}")
98
96
  return True
@@ -151,7 +149,7 @@ class ServiceWorkflowValidateCommand(ServiceCommand, ValidateCommand):
151
149
  return
152
150
 
153
151
  # Check contents of fixtures folder to confirm it's shared
154
- fixtures_folder_path = f"{STOOBLY_HOME_DIR}/{self.workflow_name}/{FIXTURES_FOLDER_NAME}"
152
+ fixtures_folder_path = f"{FIXTURES_FOLDER_NAME}"
155
153
  exec_result = container.exec_run(f"ls -A {fixtures_folder_path}")
156
154
  output = exec_result.output
157
155
 
@@ -1,8 +1,6 @@
1
- FROM stoobly/agent:1.2
1
+ FROM stoobly/agent:1.4
2
2
 
3
3
  ARG USER_ID
4
4
 
5
5
  # Change user id of stoobly user to that of host's user id
6
- RUN if [ -n "$USER_ID" ]; then usermod -u $USER_ID stoobly; fi
7
-
8
- COPY --chown=stoobly:stoobly . .
6
+ RUN if [ -n "$USER_ID" ]; then usermod -u $USER_ID stoobly; fi
@@ -4,11 +4,13 @@
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
- # STOOBLY_WORKFLOW_OPTIONS: extra options to pass to 'stoobly-agent scaffold workflow' commands
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))))
11
11
  EXEC_WORKFLOW_NAME := exec
12
+ PULL_OPTION := $(if $(STOOBLY_IMAGE_USE_LOCAL),,--pull)
13
+ USER_ID := $(shell id -u)
12
14
 
13
15
  CONTEXT_DIR_DEFAULT := $(realpath $(DIR)/../..)
14
16
 
@@ -17,36 +19,57 @@ app_dir=$$(realpath "$${STOOBLY_APP_DIR:-$(CONTEXT_DIR_DEFAULT)}")
17
19
  ca_certs_dir=$$(realpath "$${STOOBLY_CA_CERTS_DIR:-$$(realpath ~)/.mitmproxy}")
18
20
  certs_dir=$$(realpath "$${STOOBLY_CERTS_DIR:-$(app_data_dir)/certs}")
19
21
  context_dir=$$(realpath "$${STOOBLY_CONTEXT_DIR:-$(CONTEXT_DIR_DEFAULT)}")
20
- workflow_options=$${STOOBLY_WORKFLOW_OPTIONS:+$$STOOBLY_WORKFLOW_OPTIONS }
22
+
23
+ context_dir_option=--context-dir-path $(context_dir)
24
+ user_id_option=--user-id $(USER_ID)
25
+ stoobly_exec_options=--profile $(EXEC_WORKFLOW_NAME) -p $(EXEC_WORKFLOW_NAME)
26
+ workflow_down_options=$(user_id_option)
27
+ workflow_service_options=$(shell echo $$STOOBLY_WORKFLOW_SERVICE_OPTIONS)
28
+ workflow_up_options=$(context_dir_option) --ca-certs-dir-path $(ca_certs_dir) --certs-dir-path $(certs_dir) --from-make $(user_id_option)
21
29
 
22
30
  app_data_dir=$(app_dir)/.stoobly
23
- data_dir=$(context_dir)/.stoobly
31
+ app_namespace_dir=$(app_data_dir)/docker
24
32
  app_tmp_dir=$(app_data_dir)/tmp
33
+ data_dir=$(context_dir)/.stoobly
34
+ dockerfile_path=$(app_namespace_dir)/.Dockerfile.context
35
+ docker_compose_file_path=$(app_namespace_dir)/stoobly-ui/exec/.docker-compose.exec.yml
36
+ workflow_run_script=$(app_data_dir)/tmp/run.sh
25
37
 
26
38
  # Commands
27
- docker_compose_command=docker compose
39
+ docker_command=docker
40
+ docker_compose_command=$(docker_command) compose
41
+ exec_env=export CA_CERTS_DIR="$(ca_certs_dir)" && export USER_ID=$(USER_ID)
42
+ exec_up=$(docker_compose_command) -f "$(docker_compose_file_path)" $(stoobly_exec_options) up --remove-orphans
28
43
  source_env=set -a; [ -f .env ] && source .env; set +a
29
44
 
30
- docker_compose_file_path=$(app_data_dir)/docker/stoobly-ui/exec/.docker-compose.exec.yml
45
+ # Build base image
46
+ stoobly_exec_build=$(docker_command) build $(stoobly_exec_build_args) $(app_namespace_dir)
47
+ stoobly_exec_build_args=-f "$(dockerfile_path)" -t stoobly.$(USER_ID) --build-arg USER_ID=$(USER_ID) $(PULL_OPTION) --quiet
31
48
 
32
- stoobly_exec_args=--profile $(EXEC_WORKFLOW_NAME) -p $(EXEC_WORKFLOW_NAME) up --remove-orphans
33
- stoobly_exec_build=$(docker_compose_command) -f "$(docker_compose_file_path)" $(stoobly_exec_build_args)
34
- stoobly_exec_build_args=--profile $(EXEC_WORKFLOW_NAME) -p $(EXEC_WORKFLOW_NAME) build --pull --quiet
35
- stoobly_exec_env=export CONTEXT_DIR=$(context_dir) && export USER_ID=$$UID && export CA_CERTS_DIR="$(ca_certs_dir)"
49
+ # Exec any
50
+ stoobly_exec=$(stoobly_exec_build) && $(stoobly_exec_env) && $(exec_up)
51
+ stoobly_exec_env=$(source_env) && $(exec_env) && export CONTEXT_DIR="$(context_dir)"
36
52
 
37
- stoobly_exec=$(stoobly_exec_env) && $(source_env) && $(stoobly_exec_build) && $(docker_compose_command) -f "$(docker_compose_file_path)" $(stoobly_exec_args)
53
+ # Exec workflow run
54
+ # Because scaffold is stored in the application source code directory,
55
+ # when running a scaffold command from within a container,
56
+ # it needs access to $(app_dir) rather than $(context_dir)
57
+ stoobly_exec_run=$(stoobly_exec_build) && $(stoobly_exec_run_env) && $(exec_up)
58
+ stoobly_exec_run_env=$(source_env) && $(exec_env) && export CONTEXT_DIR="$(app_dir)"
38
59
 
39
- # Because scaffold is stored in the APP_DIR, when running a scaffold command from within a container,
40
- # it needs access to APP_DIR rather than CONTEXT_DIR
41
- stoobly_exec_run=$(stoobly_exec_env) && $(source_env) && $(stoobly_exec_build) && CONTEXT_DIR=$(app_dir) $(docker_compose_command) -f "$(docker_compose_file_path)" $(stoobly_exec_args)
42
-
43
- workflow_run_script=$(app_data_dir)/tmp/run.sh
44
- workflow_run_env=export APP_DIR="$(app_dir)" && export CERTS_DIR="$(certs_dir)" && export CONTEXT_DIR="$(context_dir)"
45
- workflow_run=$(workflow_run_env) && $(source_env) && bash "$(workflow_run_script)"
60
+ # Workflow run
61
+ workflow_run=$(source_env) && bash "$(workflow_run_script)"
46
62
 
63
+ ca-cert/install: stoobly/install
64
+ @echo "Running stoobly-agent ca-cert install..."; \
65
+ stoobly-agent ca-cert install
47
66
  certs:
48
67
  @export EXEC_COMMAND=bin/.mkcert && \
49
68
  $(stoobly_exec)
69
+ command/install:
70
+ $(eval COMMAND=install)
71
+ command/uninstall:
72
+ $(eval COMMAND=uninstall)
50
73
  nameservers: tmpdir
51
74
  @if [ -f /etc/resolv.conf ]; then \
52
75
  nameserver=$$(grep -Eo '([0-9]{1,3}\.){3}[0-9]{1,3}' /etc/resolv.conf) && \
@@ -56,7 +79,8 @@ nameservers: tmpdir
56
79
  fi; \
57
80
  echo "$$nameserver" > $(app_tmp_dir)/.nameservers; \
58
81
  else \
59
- echo "/etc/resolv.conf not found."; \
82
+ echo "/etc/resolv.conf not found." >&2; \
83
+ exit 1; \
60
84
  fi
61
85
  intercept/disable:
62
86
  @export EXEC_COMMAND=bin/.disable && \
@@ -65,42 +89,24 @@ intercept/enable:
65
89
  @export EXEC_COMMAND=bin/.enable && \
66
90
  export EXEC_ARGS=$(scenario_key) && \
67
91
  $(stoobly_exec)
68
- mock: nameservers
69
- @export EXEC_COMMAND=bin/.up && \
70
- export EXEC_OPTIONS="$(workflow_options)$(options)" && \
71
- export EXEC_ARGS="mock" && \
72
- $(stoobly_exec_run) && \
73
- $(workflow_run)
74
- mock/logs:
75
- @export EXEC_COMMAND=bin/.logs && \
76
- export EXEC_OPTIONS="$(workflow_options)$(options)" && \
77
- export EXEC_ARGS="mock" && \
78
- $(stoobly_exec_run) && \
79
- $(workflow_run)
80
- mock/down:
81
- @export EXEC_COMMAND=bin/.down && \
82
- export EXEC_OPTIONS="$(workflow_options)$(options)" && \
83
- export EXEC_ARGS="mock" && \
84
- $(stoobly_exec_run) && \
85
- $(workflow_run)
86
- record: nameservers
87
- @export EXEC_COMMAND=bin/.up && \
88
- export EXEC_OPTIONS="$(workflow_options)$(options)" && \
89
- export EXEC_ARGS="record" && \
90
- $(stoobly_exec_run) && \
91
- $(workflow_run)
92
- record/logs:
93
- @export EXEC_COMMAND=bin/.logs && \
94
- export EXEC_OPTIONS="$(workflow_options)$(options)" && \
95
- export EXEC_ARGS="record" && \
96
- $(stoobly_exec_run) && \
97
- $(workflow_run)
98
- record/down:
99
- @export EXEC_COMMAND=bin/.down && \
100
- export EXEC_OPTIONS="$(workflow_options)$(options)" && \
101
- export EXEC_ARGS="record" && \
102
- $(stoobly_exec_run) && \
103
- $(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
104
110
  scenario/create:
105
111
  # Create a scenario
106
112
  @export EXEC_COMMAND=bin/.create && \
@@ -130,23 +136,57 @@ scenario/snapshot:
130
136
  export EXEC_OPTIONS="$(options)" && \
131
137
  export EXEC_ARGS="$(key)" && \
132
138
  $(stoobly_exec)
133
- test:
134
- @export EXEC_COMMAND=bin/.up && \
135
- export EXEC_OPTIONS="$(workflow_options)$(options)" && \
136
- export EXEC_ARGS="test" && \
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)" && \
137
154
  $(stoobly_exec_run) && \
138
155
  $(workflow_run)
139
- test/logs:
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:
140
171
  @export EXEC_COMMAND=bin/.logs && \
141
- export EXEC_OPTIONS="$(workflow_options)$(options)" && \
142
- export EXEC_ARGS="test" && \
172
+ export EXEC_OPTIONS="$(workflow_service_options) $(options)" && \
173
+ export EXEC_ARGS="$(WORKFLOW)" && \
143
174
  $(stoobly_exec_run) && \
144
175
  $(workflow_run)
145
- test/down:
146
- @export EXEC_COMMAND=bin/.down && \
147
- export EXEC_OPTIONS="$(workflow_options)$(options)" && \
148
- export EXEC_ARGS="test" && \
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)" && \
149
191
  $(stoobly_exec_run) && \
150
- $(workflow_run)
151
- tmpdir:
152
- @mkdir -p $(app_tmp_dir)
192
+ $(workflow_run)
@@ -1,18 +1,13 @@
1
1
  services:
2
- ui_base:
3
- extends:
4
- service: context_base
5
- volumes:
6
- - ${CA_CERTS_DIR}:/home/stoobly/.mitmproxy
7
2
  context_base:
8
- build:
9
- args:
10
- USER_ID: ${USER_ID}
11
- dockerfile: ./.Dockerfile.context
3
+ extends:
4
+ service: stoobly_base
12
5
  volumes:
13
6
  - ${CONTEXT_DIR}/.stoobly:/home/stoobly/.stoobly
14
- proxy_base:
15
- build:
16
- dockerfile: ./.Dockerfile.proxy
7
+ stoobly_base:
8
+ image: stoobly.${USER_ID}
9
+ ui_base:
17
10
  extends:
18
- service: context_base
11
+ service: context_base
12
+ volumes:
13
+ - ${CA_CERTS_DIR}:/home/stoobly/.mitmproxy
@@ -1,3 +1,3 @@
1
1
  DIR := $(dir $(realpath $(lastword $(MAKEFILE_LIST))))
2
2
 
3
- include $(DIR)/.Makefile
3
+ include $(DIR).Makefile
@@ -1,11 +1,15 @@
1
1
  services:
2
2
  build.init_base:
3
- command: ['bin/.init']
3
+ command:
4
+ - bin/.init
4
5
  extends:
5
6
  file: ../.docker-compose.base.yml
6
- service: proxy_base
7
+ service: context_base
8
+ working_dir: /home/stoobly/.stoobly/docker/build/${WORKFLOW_NAME}
7
9
  build.configure_base:
8
- command: ['bin/.configure']
10
+ command:
11
+ - bin/.configure
9
12
  extends:
10
13
  file: ../.docker-compose.base.yml
11
- service: proxy_base
14
+ service: context_base
15
+ working_dir: /home/stoobly/.stoobly/docker/build/${WORKFLOW_NAME}
@@ -1,22 +1,18 @@
1
1
  services:
2
2
  build.init:
3
- build:
4
- context: ./
5
- dockerfile: ../../.Dockerfile.context
6
3
  extends:
7
4
  file: ../.docker-compose.base.yml
8
5
  service: build.init_base
6
+ image: stoobly.${USER_ID}
9
7
  profiles:
10
8
  - mock
11
9
  build.configure:
12
- build:
13
- context: ./
14
- dockerfile: ../../.Dockerfile.context
15
10
  depends_on:
16
11
  build.init:
17
12
  condition: service_completed_successfully
18
13
  extends:
19
14
  file: ../.docker-compose.base.yml
20
15
  service: build.configure_base
16
+ image: stoobly.${USER_ID}
21
17
  profiles:
22
18
  - mock
@@ -1,5 +1,8 @@
1
1
  #!/bin/bash
2
2
 
3
+ # This file was automatically generated. DO NOT EDIT.
4
+ # Any changes made to this file will be overwritten.
5
+
3
6
  stoobly-agent config reset
4
7
 
5
8
  cd $(dirname -- "$0") && ./configure
@@ -1,5 +1,8 @@
1
1
  #!/bin/bash
2
2
 
3
+ # This file was automatically generated. DO NOT EDIT.
4
+ # Any changes made to this file will be overwritten.
5
+
3
6
  stoobly-agent snapshot apply
4
7
 
5
8
  cd $(dirname -- "$0") && ./init
@@ -1,22 +1,18 @@
1
1
  services:
2
2
  build.init:
3
- build:
4
- context: ./
5
- dockerfile: ../../.Dockerfile.context
6
3
  extends:
7
4
  file: ../.docker-compose.base.yml
8
5
  service: build.init_base
6
+ image: stoobly.${USER_ID}
9
7
  profiles:
10
8
  - record
11
9
  build.configure:
12
- build:
13
- context: ./
14
- dockerfile: ../../.Dockerfile.context
15
10
  depends_on:
16
11
  build.init:
17
12
  condition: service_completed_successfully
18
13
  extends:
19
14
  file: ../.docker-compose.base.yml
20
15
  service: build.configure_base
16
+ image: stoobly.${USER_ID}
21
17
  profiles:
22
18
  - record