stoobly-agent 1.9.5__py3-none-any.whl → 1.9.7__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 CHANGED
@@ -1,2 +1,2 @@
1
1
  COMMAND = 'stoobly-agent'
2
- VERSION = '1.9.5'
2
+ VERSION = '1.9.7'
@@ -15,8 +15,18 @@ def ca_cert(ctx):
15
15
  pass
16
16
 
17
17
  @ca_cert.command()
18
- @click.option('--ca-certs-dir-path', default=DataDir.instance().ca_certs_dir_path, help='Path to ca certs directory used to sign SSL certs.')
19
- @click.option('--certs-dir-path', default=DataDir.instance().certs_dir_path, help='Output directory.')
18
+ @click.option(
19
+ '--ca-certs-dir-path',
20
+ default=DataDir.instance().ca_certs_dir_path,
21
+ help='Path to ca certs directory used to sign SSL certs.',
22
+ type=click.Path(exists=True, file_okay=False, dir_okay=True)
23
+ )
24
+ @click.option(
25
+ '--certs-dir-path',
26
+ default=DataDir.instance().certs_dir_path,
27
+ help='Output directory.',
28
+ type=click.Path(exists=True, file_okay=False, dir_okay=True)
29
+ )
20
30
  @click.argument('hostname')
21
31
  def mkcert(**kwargs):
22
32
  ca_certs_dir_path = kwargs['ca_certs_dir_path']
@@ -30,7 +40,12 @@ def mkcert(**kwargs):
30
40
  sys.exit(1)
31
41
 
32
42
  @ca_cert.command()
33
- @click.option('--ca-certs-dir-path', default=DataDir.instance().ca_certs_dir_path, help='Path to ca certs directory.')
43
+ @click.option(
44
+ '--ca-certs-dir-path',
45
+ default=DataDir.instance().ca_certs_dir_path,
46
+ help='Path to ca certs directory.',
47
+ type=click.Path(exists=True, file_okay=False, dir_okay=True)
48
+ )
34
49
  def install(**kwargs):
35
50
  ca_certs_dir_path = kwargs['ca_certs_dir_path']
36
51
  installer = CertificateAuthority(ca_certs_dir_path)
@@ -61,4 +61,5 @@ WORKFLOW_TEMPLATE = '${WORKFLOW_TEMPLATE}'
61
61
  WORKFLOW_TEMPLATE_ENV = 'WORKFLOW_TEMPLATE'
62
62
  WORKFLOW_TEST_TYPE = 'test'
63
63
 
64
+ CORE_WORKFLOWS = [WORKFLOW_MOCK_TYPE, WORKFLOW_RECORD_TYPE, WORKFLOW_TEST_TYPE]
64
65
  WORKFLOW_TEMPLATE_OPTION = Literal[WORKFLOW_MOCK_TYPE, WORKFLOW_RECORD_TYPE, WORKFLOW_TEST_TYPE]
@@ -10,7 +10,7 @@ from stoobly_agent.app.cli.scaffold.service_config import ServiceConfig
10
10
  from stoobly_agent.app.cli.scaffold.docker.constants import APP_INGRESS_NETWORK_NAME, APP_EGRESS_NETWORK_NAME, DOCKER_COMPOSE_BASE, DOCKER_COMPOSE_BASE_TEMPLATE
11
11
  from stoobly_agent.app.cli.scaffold.templates.constants import CORE_GATEWAY_SERVICE_NAME
12
12
 
13
- def configure_gateway(workflow_name, service_paths: List[str], no_publish = False):
13
+ def configure_gateway(namespace: str, service_paths: List[str], no_publish = False):
14
14
  if len(service_paths) == 0:
15
15
  return
16
16
 
@@ -36,7 +36,7 @@ def configure_gateway(workflow_name, service_paths: List[str], no_publish = Fals
36
36
  gateway_base['ports'] = ports
37
37
 
38
38
  app_dir_path = os.path.dirname(os.path.dirname(service_dir_path))
39
- __with_traefik_config(workflow_name, service_paths, app_dir_path, gateway_base)
39
+ __with_traefik_config(namespace, service_paths, app_dir_path, gateway_base)
40
40
  __with_networks(gateway_base, hostnames)
41
41
 
42
42
  with open(docker_compose_dest_path, 'w') as fp:
@@ -50,7 +50,7 @@ def __with_networks(config: dict, hostnames: List[str]):
50
50
  'aliases': hostnames
51
51
  }
52
52
 
53
- def __with_traefik_config(workflow_name: str, service_paths: List[str], app_dir_path: str, compose: dict):
53
+ def __with_traefik_config(namespace: str, service_paths: List[str], app_dir_path: str, compose: dict):
54
54
  config_dest = '/etc/traefik/traefik.yml'
55
55
 
56
56
  if not compose['volumes']:
@@ -98,7 +98,7 @@ def __with_traefik_config(workflow_name: str, service_paths: List[str], app_dir_
98
98
  })
99
99
 
100
100
  # Create traefik.yml in .stoobly/tmp
101
- traefik_template_relative_path = os.path.join(DATA_DIR_NAME, TMP_DIR_NAME, workflow_name, 'traefik.yml')
101
+ traefik_template_relative_path = os.path.join(DATA_DIR_NAME, TMP_DIR_NAME, namespace, 'traefik.yml')
102
102
  traefik_template_dest_path = os.path.join(app_dir_path, traefik_template_relative_path)
103
103
 
104
104
  if not os.path.exists(os.path.dirname(traefik_template_dest_path)):
@@ -20,5 +20,11 @@ class Service():
20
20
  def service_name(self):
21
21
  return self.__service_name
22
22
 
23
+ @property
24
+ def workflows(self):
25
+ # Each directory in the service directory path is a workflow
26
+ workflows = [ f.name for f in os.scandir(self.dir_path) if f.is_dir() ]
27
+ return workflows
28
+
23
29
  def workflow_dir_path(self, workflow_name: str):
24
30
  return os.path.join(self.dir_path, workflow_name)
@@ -18,6 +18,10 @@ class ServiceCommand(AppCommand):
18
18
  def service_config(self):
19
19
  return self.__config
20
20
 
21
+ @service_config.setter
22
+ def service_config(self, value):
23
+ self.__config = value
24
+
21
25
  @property
22
26
  def service_config_path(self):
23
27
  return self.__config.path
@@ -26,6 +30,10 @@ class ServiceCommand(AppCommand):
26
30
  def service_name(self):
27
31
  return self.__service_name
28
32
 
33
+ @service_name.setter
34
+ def service_name(self, value):
35
+ self.__service_name = value
36
+
29
37
  @property
30
38
  def service_exists(self):
31
39
  return os.path.exists(self.service_path)
@@ -0,0 +1,70 @@
1
+ import os
2
+ import pdb
3
+
4
+ from stoobly_agent.app.cli.scaffold.app import App
5
+ from stoobly_agent.app.cli.scaffold.constants import CORE_WORKFLOWS
6
+ from stoobly_agent.app.cli.scaffold.docker.workflow.decorators_factory import get_workflow_decorators
7
+ from stoobly_agent.app.cli.scaffold.service import Service
8
+ from stoobly_agent.app.cli.scaffold.service_command import ServiceCommand
9
+ from stoobly_agent.app.cli.scaffold.service_create_command import ServiceCreateCommand
10
+ from stoobly_agent.app.cli.scaffold.service_config import ServiceConfig
11
+ from stoobly_agent.app.cli.scaffold.workflow_config import WorkflowConfig
12
+ from stoobly_agent.app.cli.scaffold.workflow_create_command import WorkflowCreateCommand
13
+
14
+ from stoobly_agent.lib.logger import Logger
15
+
16
+
17
+ LOG_ID = 'ServiceUpdateCommand'
18
+
19
+ class ServiceUpdateCommand(ServiceCommand):
20
+
21
+ def __init__(self, app: App, **kwargs):
22
+ super().__init__(app, **kwargs)
23
+
24
+ def rename(self, new_service_name: str) -> Service:
25
+ self.__rename_service_dir(self.service_path, new_service_name)
26
+
27
+ self.service = Service(new_service_name, self.app)
28
+ self.service_config = ServiceConfig(self.service.dir_path)
29
+
30
+ self.__update_internal_container_specs(new_service_name)
31
+
32
+ return self.service
33
+
34
+ def __rename_service_dir(self, dir_path: str, new_name: str) -> None:
35
+ new_dir_path = os.path.join(self.app.scaffold_namespace_path, new_name)
36
+ os.rename(dir_path, new_dir_path)
37
+
38
+ def __update_internal_container_specs(self, new_service_name: str) -> None:
39
+ workflows = self.service.workflows
40
+
41
+ kwargs = {}
42
+ kwargs['app_dir_path'] = self.app.dir_path
43
+ kwargs['service_name'] = new_service_name
44
+ kwargs['workflow'] = workflows
45
+ command = ServiceCreateCommand(self.app, **kwargs)
46
+ command.build()
47
+
48
+ custom_workflows_set = set(workflows) - set(CORE_WORKFLOWS)
49
+ custom_workflows = list(custom_workflows_set)
50
+
51
+ for workflow in custom_workflows:
52
+ kwargs = {}
53
+ kwargs['app_dir_path'] = self.app.dir_path
54
+ kwargs['force'] = True
55
+ kwargs['service_name'] = new_service_name
56
+ kwargs['workflow_name'] = workflow
57
+
58
+ workflow_path = self.service.workflow_dir_path(workflow)
59
+ workflow_config = WorkflowConfig(workflow_path, **kwargs)
60
+ kwargs['template'] = workflow_config.template
61
+
62
+ command = WorkflowCreateCommand(self.app, **kwargs)
63
+
64
+ service_config = self.service_config
65
+ workflow_decorators = get_workflow_decorators(kwargs['template'], service_config)
66
+
67
+ command.build(
68
+ template=kwargs['template'],
69
+ workflow_decorators=workflow_decorators
70
+ )
@@ -116,12 +116,13 @@ class ServiceWorkflowValidateCommand(ServiceCommand, ValidateCommand):
116
116
 
117
117
  def validate_internal_hostname(self, url: str) -> None:
118
118
  print(f"Validating hostname inside Docker network, url: {url}")
119
-
119
+
120
+ network = f"{self.workflow_name}.{self.app_config.network}"
120
121
  timeout_seconds = 1
121
122
  output = self.docker_client.containers.run(
122
123
  image='curlimages/curl:8.11.0',
123
124
  command=f"curl --max-time {timeout_seconds} {url} --verbose",
124
- network=APP_EGRESS_NETWORK_TEMPLATE.format(network=self.app_config.network),
125
+ network=APP_EGRESS_NETWORK_TEMPLATE.format(network=network),
125
126
  stderr=True,
126
127
  remove=True,
127
128
  )
@@ -35,7 +35,7 @@ dockerfile_path=$(app_namespace_dir)/.Dockerfile.context
35
35
  exec_docker_compose_file_path=$(app_namespace_dir)/stoobly-ui/exec/.docker-compose.exec.yml
36
36
  exec_namespace=$(shell echo "$(workflow_namespace).$(context_dir)" | (md5 2>/dev/null || md5sum 2>/dev/null || shasum 2>/dev/null) | awk '{print $$1}')
37
37
  workflow_namespace=$(if $(namespace),$(namespace),$(workflow))
38
- workflow_script=.stoobly/tmp/$(workflow)/run.sh
38
+ workflow_script=.stoobly/tmp/$(workflow_namespace)/run.sh
39
39
 
40
40
  # Options
41
41
  certs_dir_options=--ca-certs-dir-path $(ca_certs_dir) --certs-dir-path $(certs_dir)
@@ -52,8 +52,8 @@ docker_command=docker
52
52
  docker_compose_command=$(docker_command) compose
53
53
  exec_down=$(docker_compose_command) -f "$(exec_docker_compose_file_path)" $(stoobly_exec_options) down
54
54
  exec_env=APP_DIR="$(app_dir)" CA_CERTS_DIR="$(ca_certs_dir)" USER_ID="$(USER_ID)"
55
- exec_up=$(docker_compose_command) -f "$(exec_docker_compose_file_path)" $(stoobly_exec_options) up --remove-orphans
56
- source_env=set -a; [ -f .env ] && source .env; set +a
55
+ exec_up=$(docker_compose_command) -f "$(exec_docker_compose_file_path)" $(stoobly_exec_options) up --abort-on-container-exit --remove-orphans
56
+ source_env=(set -a; [ -f .env ] && source .env; set +a)
57
57
 
58
58
  # Build base image
59
59
  stoobly_exec_build=$(docker_command) build $(stoobly_exec_build_args) $(app_namespace_dir)
@@ -3,11 +3,10 @@ import pdb
3
3
  import shutil
4
4
 
5
5
  from .app import App
6
- from .constants import COMPOSE_TEMPLATE, WORKFLOW_MOCK_TYPE, WORKFLOW_RECORD_TYPE, WORKFLOW_TEST_TYPE
6
+ from .constants import COMPOSE_TEMPLATE
7
7
  from .service_workflow import ServiceWorkflow
8
8
  from .workflow_command import WorkflowCommand
9
9
 
10
- CORE_WORKFLOWS = [WORKFLOW_MOCK_TYPE, WORKFLOW_RECORD_TYPE, WORKFLOW_TEST_TYPE]
11
10
 
12
11
  class WorkflowCopyCommand(WorkflowCommand):
13
12
 
@@ -5,7 +5,7 @@ import shutil
5
5
  from typing import List, TypedDict, Union
6
6
 
7
7
  from .app import App
8
- from .constants import WORKFLOW_MOCK_TYPE, WORKFLOW_RECORD_TYPE, WORKFLOW_TEST_TYPE, WORKFLOW_TEMPLATE_OPTION
8
+ from .constants import WORKFLOW_TEMPLATE_OPTION
9
9
  from .docker.service.builder import ServiceBuilder
10
10
  from .docker.workflow.mock_decorator import MockDecorator
11
11
  from .docker.workflow.reverse_proxy_decorator import ReverseProxyDecorator
@@ -13,7 +13,6 @@ from .docker.workflow.builder import WorkflowBuilder
13
13
  from .templates.factory import custom_files, maintained_files
14
14
  from .workflow_command import WorkflowCommand
15
15
 
16
- CORE_WORKFLOWS = [WORKFLOW_MOCK_TYPE, WORKFLOW_RECORD_TYPE, WORKFLOW_TEST_TYPE]
17
16
 
18
17
  class BuildOptions(TypedDict):
19
18
  builder_class: type
@@ -48,8 +48,8 @@ class WorkflowRunCommand(WorkflowCommand):
48
48
  self.__ca_certs_dir_path = kwargs.get('ca_certs_dir_path') or app.ca_certs_dir_path
49
49
  self.__certs_dir_path = kwargs.get('certs_dir_path') or app.certs_dir_path
50
50
  self.__context_dir_path = kwargs.get('context_dir_path') or app.context_dir_path
51
- self.__network = kwargs.get('network') or self.app_config.network
52
-
51
+ self.__network = f"{kwargs.get('network') or {self.workflow_name}}.{self.app_config.network}"
52
+
53
53
  @property
54
54
  def app_dir_path(self):
55
55
  return self.__app_dir_path
@@ -1,7 +1,6 @@
1
1
  import click
2
2
  import os
3
3
  import pdb
4
- import re
5
4
  import sys
6
5
 
7
6
  from io import TextIOWrapper
@@ -23,6 +22,7 @@ from stoobly_agent.app.cli.scaffold.service import Service
23
22
  from stoobly_agent.app.cli.scaffold.service_config import ServiceConfig
24
23
  from stoobly_agent.app.cli.scaffold.service_create_command import ServiceCreateCommand
25
24
  from stoobly_agent.app.cli.scaffold.service_delete_command import ServiceDeleteCommand
25
+ from stoobly_agent.app.cli.scaffold.service_update_command import ServiceUpdateCommand
26
26
  from stoobly_agent.app.cli.scaffold.service_workflow_validate_command import ServiceWorkflowValidateCommand
27
27
  from stoobly_agent.app.cli.scaffold.templates.constants import CORE_SERVICES
28
28
  from stoobly_agent.app.cli.scaffold.validate_exceptions import ScaffoldValidateException
@@ -37,6 +37,7 @@ from stoobly_agent.config.data_dir import DataDir
37
37
  from stoobly_agent.lib.logger import bcolors, DEBUG, ERROR, INFO, Logger, WARNING
38
38
 
39
39
  from .helpers.print_service import FORMATS, print_services, select_print_options
40
+ from .validators.scaffold import validate_app_name, validate_hostname, validate_namespace, validate_service_name
40
41
 
41
42
  LOG_ID = 'Scaffold'
42
43
 
@@ -90,7 +91,7 @@ def hostname(ctx):
90
91
  @click.option('--network', help='App default network name. Defaults to app name.')
91
92
  @click.option('--quiet', is_flag=True, help='Disable log output.')
92
93
  @click.option('--ui-port', default=4200, type=click.IntRange(1, 65535), help='UI service port.')
93
- @click.argument('app_name')
94
+ @click.argument('app_name', callback=validate_app_name)
94
95
  def create(**kwargs):
95
96
  __validate_app_dir(kwargs['app_dir_path'])
96
97
 
@@ -129,7 +130,7 @@ def mkcert(**kwargs):
129
130
  @click.option('--app-dir-path', default=current_working_dir, help='Path to application directory.')
130
131
  @click.option('--detached', is_flag=True, help='Use isolated and non-persistent context directory.')
131
132
  @click.option('--env', multiple=True, help='Specify an environment variable.')
132
- @click.option('--hostname', help='Service hostname.')
133
+ @click.option('--hostname', callback=validate_hostname, help='Service hostname.')
133
134
  @click.option('--port', type=click.IntRange(1, 65535), help='Service port.')
134
135
  @click.option('--priority', default=5, type=click.FloatRange(1.0, 9.0), help='Determines the service run order. Lower values run first.')
135
136
  @click.option('--proxy-mode', help='''
@@ -141,17 +142,10 @@ def mkcert(**kwargs):
141
142
  @click.option('--quiet', is_flag=True, help='Disable log output.')
142
143
  @click.option('--scheme', type=click.Choice(['http', 'https']), help='Defaults to https if hostname is set.')
143
144
  @click.option('--workflow', multiple=True, type=click.Choice([WORKFLOW_MOCK_TYPE, WORKFLOW_RECORD_TYPE, WORKFLOW_TEST_TYPE]), help='Include pre-defined workflows.')
144
- @click.argument('service_name')
145
+ @click.argument('service_name', callback=validate_service_name)
145
146
  def create(**kwargs):
146
147
  __validate_app_dir(kwargs['app_dir_path'])
147
148
 
148
- if '/' in kwargs['service_name']:
149
- print(f"Error: {kwargs['service_name']} is invalid. It cannot container '/", file=sys.stderr)
150
- sys.exit(1)
151
-
152
- if kwargs.get('hostname'):
153
- __validate_hostname(kwargs.get('hostname'))
154
-
155
149
  if kwargs.get("proxy_mode"):
156
150
  __validate_proxy_mode(kwargs.get("proxy_mode"))
157
151
 
@@ -214,11 +208,11 @@ def delete(**kwargs):
214
208
  help="Update a service config"
215
209
  )
216
210
  @click.option('--app-dir-path', default=current_working_dir, help='Path to application directory.')
217
- @click.option('--hostname', help='Service hostname.')
211
+ @click.option('--hostname', callback=validate_hostname, help='Service hostname.')
218
212
  @click.option('--port', type=click.IntRange(1, 65535), help='Service port.')
219
213
  @click.option('--priority', default=5, type=click.FloatRange(1.0, 9.0), help='Determines the service run order. Lower values run first.')
220
214
  @click.option('--scheme', type=click.Choice(['http', 'https']), help='Defaults to https if hostname is set.')
221
- @click.option('--name', type=click.STRING, help='New name of the service to update to.')
215
+ @click.option('--name', callback=validate_service_name, type=click.STRING, help='New name of the service to update to.')
222
216
  @click.option('--proxy-mode', help='''
223
217
  Proxy mode can be "regular", "transparent", "socks5",
224
218
  "reverse:SPEC", or "upstream:SPEC". For reverse and
@@ -237,8 +231,6 @@ def update(**kwargs):
237
231
  service_config = ServiceConfig(service.dir_path)
238
232
 
239
233
  if kwargs['hostname']:
240
- __validate_hostname(kwargs['hostname'])
241
-
242
234
  old_hostname = service_config.hostname
243
235
 
244
236
  if old_hostname != kwargs['hostname']:
@@ -261,6 +253,19 @@ def update(**kwargs):
261
253
  if kwargs['scheme']:
262
254
  service_config.scheme = kwargs['scheme']
263
255
 
256
+ if kwargs['name']:
257
+ old_service_name = service.service_name
258
+ new_service_name = kwargs['name']
259
+
260
+ print(f"Renaming service from: {old_service_name}, to: {new_service_name}")
261
+
262
+ kwargs['service_path'] = service.dir_path
263
+ command = ServiceUpdateCommand(app, **kwargs)
264
+ service = command.rename(new_service_name)
265
+ service_config = command.service_config
266
+
267
+ print(f"Successfully renamed service to: {new_service_name}")
268
+
264
269
  if kwargs['proxy_mode']:
265
270
  __validate_proxy_mode(kwargs['proxy_mode'])
266
271
  service_config.proxy_mode = kwargs['proxy_mode']
@@ -329,7 +334,7 @@ def copy(**kwargs):
329
334
  @click.option('--log-level', default=INFO, type=click.Choice([DEBUG, INFO, WARNING, ERROR]), help='''
330
335
  Log levels can be "debug", "info", "warning", or "error"
331
336
  ''')
332
- @click.option('--namespace', help='Workflow namespace.')
337
+ @click.option('--namespace', callback=validate_namespace, help='Workflow namespace.')
333
338
  @click.option('--network', help='Workflow network name.')
334
339
  @click.option('--rmi', is_flag=True, help='Remove images used by containers.')
335
340
  @click.option('--script-path', help='Path to intermediate script path.')
@@ -342,9 +347,7 @@ def down(**kwargs):
342
347
  app = App(kwargs['app_dir_path'], DOCKER_NAMESPACE, **kwargs)
343
348
  __validate_app(app)
344
349
 
345
- # If namespace is set, default network to namespace
346
- if kwargs['namespace'] and not kwargs['network']:
347
- kwargs['network'] = kwargs['namespace']
350
+ __with_namespace_defaults(kwargs)
348
351
 
349
352
  services = __get_services(
350
353
  app, service=kwargs['service'], workflow=[kwargs['workflow_name']]
@@ -412,7 +415,7 @@ def down(**kwargs):
412
415
  @click.option('--log-level', default=INFO, type=click.Choice([DEBUG, INFO, WARNING, ERROR]), help='''
413
416
  Log levels can be "debug", "info", "warning", or "error"
414
417
  ''')
415
- @click.option('--namespace', help='Workflow namespace.')
418
+ @click.option('--namespace', callback=validate_namespace, help='Workflow namespace.')
416
419
  @click.option('--script-path', help='Path to intermediate script path.')
417
420
  @click.option('--service', multiple=True, help='Select which services to log. Defaults to all.')
418
421
  @click.argument('workflow_name')
@@ -422,6 +425,8 @@ def logs(**kwargs):
422
425
  app = App(kwargs['app_dir_path'], DOCKER_NAMESPACE)
423
426
  __validate_app(app)
424
427
 
428
+ __with_namespace_defaults(kwargs)
429
+
425
430
  if len(kwargs['container']) == 0:
426
431
  kwargs['container'] = [WORKFLOW_CONTAINER_PROXY]
427
432
 
@@ -474,7 +479,7 @@ def logs(**kwargs):
474
479
  Log levels can be "debug", "info", "warning", or "error"
475
480
  ''')
476
481
  @click.option('--mkcert', is_flag=True, help='Set to generate SSL certs for HTTPS services.')
477
- @click.option('--namespace', help='Workflow namespace.')
482
+ @click.option('--namespace', callback=validate_namespace, help='Workflow namespace.')
478
483
  @click.option('--network', help='Workflow network name.')
479
484
  @click.option('--no-build', is_flag=True, help='Do not build images before starting containers.')
480
485
  @click.option('--no-publish', is_flag=True, help='Do not publish all ports.')
@@ -496,9 +501,7 @@ def up(**kwargs):
496
501
  app = App(app_dir_path, DOCKER_NAMESPACE, **kwargs)
497
502
  __validate_app(app)
498
503
 
499
- # If namespace is set, default network to namespace
500
- if kwargs['namespace'] and not kwargs['network']:
501
- kwargs['network'] = kwargs['namespace']
504
+ __with_namespace_defaults(kwargs)
502
505
 
503
506
  services = __get_services(
504
507
  app, service=kwargs['service'], workflow=[kwargs['workflow_name']]
@@ -677,7 +680,7 @@ def __build_script(**kwargs):
677
680
  script_path = kwargs['script_path']
678
681
  if not script_path:
679
682
  script_file_name = 'run.sh'
680
- script_path = os.path.join(data_dir.tmp_dir_path, kwargs['workflow_name'], script_file_name)
683
+ script_path = os.path.join(data_dir.tmp_dir_path, kwargs.get('namespace') or kwargs['workflow_name'] or '', script_file_name)
681
684
 
682
685
  script_dir = os.path.dirname(script_path)
683
686
  if not os.path.exists(script_dir):
@@ -818,11 +821,13 @@ def __validate_proxy_mode(proxy_mode: str) -> None:
818
821
 
819
822
  # TODO: validate SPEC
820
823
 
821
- def __validate_hostname(hostname: str) -> None:
822
- hostname_regex = re.compile(r'^[a-zA-Z0-9.-]+$')
823
- if not re.search(hostname_regex, hostname):
824
- print(f"Error: {hostname} is invalid.", file=sys.stderr)
825
- sys.exit(1)
824
+ def __with_namespace_defaults(kwargs):
825
+ if not kwargs.get('namespace'):
826
+ kwargs['namespace'] = kwargs.get('workflow_name')
827
+
828
+ # If network there was a network option, but it is not set, default network to namespace
829
+ if 'network' in kwargs and not kwargs['network']:
830
+ kwargs['network'] = kwargs['namespace']
826
831
 
827
832
  def __workflow_create(app, **kwargs):
828
833
  command = WorkflowCreateCommand(app, **kwargs)
@@ -833,4 +838,4 @@ def __workflow_create(app, **kwargs):
833
838
  command.build(
834
839
  template=kwargs['template'],
835
840
  workflow_decorators=workflow_decorators
836
- )
841
+ )
@@ -0,0 +1,39 @@
1
+ import re
2
+ import sys
3
+
4
+ from stoobly_agent.app.cli.scaffold.templates.constants import CORE_SERVICES
5
+
6
+ def validate_app_name(ctx, param, app_name: str) -> str:
7
+ app_name_regex = re.compile(r'^[a-zA-Z0-9._-]+$')
8
+ if not re.search(app_name_regex, app_name):
9
+ print(f"Error: app name {app_name} is invalid.", file=sys.stderr)
10
+ sys.exit(1)
11
+ return app_name
12
+
13
+ def validate_hostname(ctx, param, hostname: str) -> str:
14
+ if not hostname:
15
+ return
16
+ hostname_regex = re.compile(r'^[a-zA-Z0-9.-]+$')
17
+ if not re.search(hostname_regex, hostname):
18
+ print(f"Error: hostname {hostname} is invalid.", file=sys.stderr)
19
+ sys.exit(1)
20
+ return hostname
21
+
22
+ def validate_namespace(ctx, param, namespace: str) -> str:
23
+ if not namespace:
24
+ return
25
+ namespace_regex = re.compile(r'^[a-z0-9_-]+$')
26
+ if not re.search(namespace_regex, namespace) or namespace[0] in ['-', '_']:
27
+ print(f"Error: namespace {namespace} is invalid.", file=sys.stderr)
28
+ sys.exit(1)
29
+ return namespace
30
+
31
+ def validate_service_name(ctx, param, service_name: str) -> str:
32
+ if service_name in CORE_SERVICES:
33
+ print(f"Error: {service_name} is a core service", file=sys.stderr)
34
+ sys.exit(1)
35
+ service_name_regex = re.compile(r'^[a-zA-Z0-9._-]+$')
36
+ if not re.search(service_name_regex, service_name):
37
+ print(f"Error: service name {service_name} is invalid.", file=sys.stderr)
38
+ sys.exit(1)
39
+ return service_name
@@ -1 +1 @@
1
- 1.9.4
1
+ 1.9.6
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: stoobly-agent
3
- Version: 1.9.5
3
+ Version: 1.9.7
4
4
  Summary: Record, mock, and test HTTP(s) requests. CLI agent for Stoobly
5
5
  License: Apache-2.0
6
6
  Author: Matt Le
@@ -1,4 +1,4 @@
1
- stoobly_agent/__init__.py,sha256=IIieB06ghDX3rkbVoznhopSYzZNIgt8A-_fJzdVS7i4,44
1
+ stoobly_agent/__init__.py,sha256=Hl61BiQl9f9AF1FOpbPAtXQt4rJsqBc_U216ZisxBQU,44
2
2
  stoobly_agent/app/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
3
  stoobly_agent/app/api/__init__.py,sha256=ctkB8KR-eXO0SFhj602huHiyvQ3PslFWd8fkcufgrAI,1000
4
4
  stoobly_agent/app/api/application_http_request_handler.py,sha256=Vvz53yB0bR7J-QqMAkLlhcZrA4P64ZEN7w8cMbgl6o0,5261
@@ -17,7 +17,7 @@ stoobly_agent/app/api/scenarios_controller.py,sha256=zJViyme9l36mlJDQONRu4N-A0kd
17
17
  stoobly_agent/app/api/simple_http_request_handler.py,sha256=ZdJ4kKEcPDpck74dJu--M7zw76wQzpo_AQArZwN9Bes,4466
18
18
  stoobly_agent/app/api/statuses_controller.py,sha256=gQsl28h-3mZ_bByEHpc4mRy3KFvUz5PeAgVB0cPx_Ao,1791
19
19
  stoobly_agent/app/cli/__init__.py,sha256=uS4KJgMAhm0JVnEUC-ONW-Bq6A8I9v7Fk-_Ka7gXlrU,470
20
- stoobly_agent/app/cli/ca_cert_cli.py,sha256=qwuNXRJTi4hLHObv8uVKd2XeSsS3A_T-OjztjVctrkI,1387
20
+ stoobly_agent/app/cli/ca_cert_cli.py,sha256=KVIUmmZcMHW2UXZJTmXqTVk2lEbp0YbxB6C4EL5t2Q4,1629
21
21
  stoobly_agent/app/cli/config_cli.py,sha256=Ljr2Ir8P0dhGXALQQcq7fwthrcipPlvxS2G_0bNE6fY,15727
22
22
  stoobly_agent/app/cli/decorators/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
23
23
  stoobly_agent/app/cli/decorators/config.py,sha256=AWrDGZm_gjCWFYlRwdla3iE6H7OSlM4FxkaXRNovBZA,2428
@@ -73,7 +73,7 @@ stoobly_agent/app/cli/scaffold/app_config.py,sha256=UGVJ7DVmXh-o_gYBlAAEjngNIUZP
73
73
  stoobly_agent/app/cli/scaffold/app_create_command.py,sha256=0ogYliGbq1PYP5rFs-ML33q_Z4t_rAgPWjhk7rhnGw0,1153
74
74
  stoobly_agent/app/cli/scaffold/command.py,sha256=aoTsdkkBzyu7TkVSMdNQQGk0Gj874jNgFcjR14y3TuM,254
75
75
  stoobly_agent/app/cli/scaffold/config.py,sha256=HZU5tkvr3dkPr4JMXZtrJlu2wxxO-134Em6jReFFcq0,688
76
- stoobly_agent/app/cli/scaffold/constants.py,sha256=dpCGvKzNtGQyaZfghfy0F6bJsZNp1KPACX3Z3rd13sw,2416
76
+ stoobly_agent/app/cli/scaffold/constants.py,sha256=e0aKyB80i1heJCubAvxt8DlzykAvYH37xeAcl9UTTgI,2496
77
77
  stoobly_agent/app/cli/scaffold/containerized_app.py,sha256=dAjn4RwcZV3aEL0POUmrbF_DC-r9h6s1zx7gT2t45v0,175
78
78
  stoobly_agent/app/cli/scaffold/docker/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
79
79
  stoobly_agent/app/cli/scaffold/docker/app_builder.py,sha256=7z5pk5JKlRDHx2USxY-WurttLyyUkIVYfl34_u1x9dE,501
@@ -82,7 +82,7 @@ stoobly_agent/app/cli/scaffold/docker/constants.py,sha256=1khQdgTaQ89ykGRNdTQh_e
82
82
  stoobly_agent/app/cli/scaffold/docker/service/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
83
83
  stoobly_agent/app/cli/scaffold/docker/service/build_decorator.py,sha256=ZU7z4bkvdS3OK5O4fJhlA9_PNwnFtZW6t7vNF7V5obQ,1003
84
84
  stoobly_agent/app/cli/scaffold/docker/service/builder.py,sha256=4cIMSYvgrkGWVuuYymiwlrR829O91qQl9ML8FhaDMj4,5857
85
- stoobly_agent/app/cli/scaffold/docker/service/configure_gateway.py,sha256=jtx8lhvKHl0ubSHaQpE8m9lCYnOJ6Qju-SBJPBs0l7I,3921
85
+ stoobly_agent/app/cli/scaffold/docker/service/configure_gateway.py,sha256=8GQ2wUBwb45uSxhzkEMloezJT1hVu8v_3PudWLTU9ig,3910
86
86
  stoobly_agent/app/cli/scaffold/docker/service/types.py,sha256=qB-yYHlu-PZDc0HYgTUvE5bWNpHxaSThC3JUG8okR1k,88
87
87
  stoobly_agent/app/cli/scaffold/docker/workflow/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
88
88
  stoobly_agent/app/cli/scaffold/docker/workflow/build_decorator.py,sha256=VKD9hXbJGRIWHS5IeYXeX0-FQ0F43zG8VmsegL6eYwA,703
@@ -94,17 +94,18 @@ stoobly_agent/app/cli/scaffold/docker/workflow/reverse_proxy_decorator.py,sha256
94
94
  stoobly_agent/app/cli/scaffold/env.py,sha256=e-Ve4p3RUgzFx22B3SIYttvJ_yLuDtA27oDACZ8n-6E,1140
95
95
  stoobly_agent/app/cli/scaffold/hosts_file_manager.py,sha256=zNX5wh6zXQ4J2BA0YYdD7_CPqDz02b_ghXsY3oTjjB4,4999
96
96
  stoobly_agent/app/cli/scaffold/managed_services_docker_compose.py,sha256=-wLBXUi7DCWsfm5KzZzd_kdJKOTl1NT924XR7dyjbSY,574
97
- stoobly_agent/app/cli/scaffold/service.py,sha256=L9K6QE0k5KSEC8_fSwtdwwTSO_DsIpqSPW-AG7Bg76o,501
98
- stoobly_agent/app/cli/scaffold/service_command.py,sha256=9kIKiFC5Jo425VWYD4NDvUOdMP-pNyq2D5Ip1ZAPj3A,1054
97
+ stoobly_agent/app/cli/scaffold/service.py,sha256=74JwjTRRkk6lo-k9hre1iGztbKa9zDqjPVx3Qgpze-s,699
98
+ stoobly_agent/app/cli/scaffold/service_command.py,sha256=NxJakoq1Dy79LEIJ8QSsKEwsofIfN13GN0UGpp9i6qY,1230
99
99
  stoobly_agent/app/cli/scaffold/service_config.py,sha256=edL356wqpfJgFdeSrWof-CAUB1Tgi4nO1Jx9nkql9Qc,3903
100
100
  stoobly_agent/app/cli/scaffold/service_create_command.py,sha256=1B6TK3JDAjouikCV84WDrX7b0crdPMPIo1caMwhi-L8,2815
101
101
  stoobly_agent/app/cli/scaffold/service_delete_command.py,sha256=_nBDQjm8eL62MQpzSCxgUHlW04ZXKG8MDlN1BXxlqww,986
102
102
  stoobly_agent/app/cli/scaffold/service_docker_compose.py,sha256=OMUN1-ujQYIZXxDvS4XBf5C9wGalQULkwOiBBQPZbHY,820
103
+ stoobly_agent/app/cli/scaffold/service_update_command.py,sha256=oWusBKfvjt4RnK03_V3CJYWrfsCI4_LcR7W12eLXMR4,2579
103
104
  stoobly_agent/app/cli/scaffold/service_workflow.py,sha256=sQ_Edy_wGHKMXpD0DmhnOWkGEKz7gSgEGNI8f7aXOdg,444
104
- stoobly_agent/app/cli/scaffold/service_workflow_validate_command.py,sha256=xONRUtfC3IBd-Kr4wdUKWgx9ppSsbu2H72pb2VinizQ,11412
105
+ stoobly_agent/app/cli/scaffold/service_workflow_validate_command.py,sha256=veigidzR4EwGi8dc0v_l4Ik7cZikDBnLcvwcHNc1Wzg,11457
105
106
  stoobly_agent/app/cli/scaffold/templates/__init__.py,sha256=x8C_a0VoO_vUbosp4_6IC1U7Ge9NnUdVKDPpVMtMkeY,171
106
107
  stoobly_agent/app/cli/scaffold/templates/app/.Dockerfile.context,sha256=Okk4Q0Fj7Wi5NU58gQfpjpFwAL3RUBJyRe56kteQfcA,158
107
- stoobly_agent/app/cli/scaffold/templates/app/.Makefile,sha256=zifPWDDZ9RPz0p872Kl00pnmFY3jMHulp84tYXhfSwg,9240
108
+ stoobly_agent/app/cli/scaffold/templates/app/.Makefile,sha256=4CTrKRHho9fl7NOGrQM0lJTfn-X09ddQKBq9Zsbjbww,9278
108
109
  stoobly_agent/app/cli/scaffold/templates/app/.docker-compose.base.yml,sha256=6tFqXh3ine8vaD0FCL5TMoY5NjKx2wLUR8XpW3tJtew,245
109
110
  stoobly_agent/app/cli/scaffold/templates/app/.docker-compose.networks.yml,sha256=I4PbJpQjFHb5IbAUWNvYM6okDEtmwtKFDQg-yog05WM,141
110
111
  stoobly_agent/app/cli/scaffold/templates/app/Makefile,sha256=TEmPG7Bf0KZOnmfsgdzza3UdwcVMmM5Lj1YdLc4cgjA,79
@@ -197,13 +198,13 @@ stoobly_agent/app/cli/scaffold/validate_exceptions.py,sha256=Jtjl4OkbbSRWm0hy7Kf
197
198
  stoobly_agent/app/cli/scaffold/workflow.py,sha256=KlbWT9CIo9EpZxKU1WVZtmodhxK7CpmLUHPNk4Mh6DA,1570
198
199
  stoobly_agent/app/cli/scaffold/workflow_command.py,sha256=eI9I5LLgO0U3b46QhHusy-4BV2zUDVai6jErcluYCRI,3344
199
200
  stoobly_agent/app/cli/scaffold/workflow_config.py,sha256=ghnbcnCyb6ECdylUJCAJ6A8ulzaFY9bu7RvRuYeiRkk,901
200
- stoobly_agent/app/cli/scaffold/workflow_copy_command.py,sha256=R9hh5dWVz7vGld7pENAA_a9gW56EkgG2K35nBQYXwyI,1462
201
- stoobly_agent/app/cli/scaffold/workflow_create_command.py,sha256=R3L1obUTidGngbIaRlVjt_Rjkw9qOPze_p5plHc8JHs,3532
201
+ stoobly_agent/app/cli/scaffold/workflow_copy_command.py,sha256=wi8qHH_M2e6jXIPuumx65Zd4Zt7hTBb6b3Z4vt4xYeQ,1320
202
+ stoobly_agent/app/cli/scaffold/workflow_create_command.py,sha256=-fwsr6_LvGT8BbBWdGY3Qd8cSQhBOSJiMr1r8s2R86w,3390
202
203
  stoobly_agent/app/cli/scaffold/workflow_env.py,sha256=x8V5pJmIiklD3f2q2-qq-CORf4YaXYq_r2JpR2MmSwk,416
203
204
  stoobly_agent/app/cli/scaffold/workflow_log_command.py,sha256=Bke4lMOMxuDUFuAx9nlXHbKgYMO4KAg9ASHvjz4aVWc,1372
204
- stoobly_agent/app/cli/scaffold/workflow_run_command.py,sha256=eF3aaK4OIZXYuSBEAeBnhAL7EZrS1G4mSYrJbEiXt2o,11082
205
+ stoobly_agent/app/cli/scaffold/workflow_run_command.py,sha256=AZhLd1N260RjXEiUSRppqfhQMwVyRnn0jpapRu4FwyM,11114
205
206
  stoobly_agent/app/cli/scaffold/workflow_validate_command.py,sha256=Uo_yo6rVR1ZR7xpvsQvlH48AyMBVLRupd4G-bRjzm_Q,5584
206
- stoobly_agent/app/cli/scaffold_cli.py,sha256=NXlLuqAwv9p0dflksswAtgJPTfojvJDDHIdJ71-BCSE,31965
207
+ stoobly_agent/app/cli/scaffold_cli.py,sha256=3V4F_Bd8th6p_nu6u82qzncVBdqNd5cRJZaj7tyIdxA,32492
207
208
  stoobly_agent/app/cli/scenario_cli.py,sha256=3J1EiJOvunkfWrEkOsanw-XrKkOk78ij_GjBlE9p7CE,8229
208
209
  stoobly_agent/app/cli/snapshot_cli.py,sha256=1Dw5JgDlmG6vctrawIRO7CdB73vAQk_wRBnPG2lVOrQ,11929
209
210
  stoobly_agent/app/cli/trace_cli.py,sha256=K7E-vx3JUcqEDSWOdIOi_AieKNQz7dBfmRrVvKDkzFI,4605
@@ -214,6 +215,7 @@ stoobly_agent/app/cli/types/request.py,sha256=QthojE5sfx7OvKu-vVNnSUfGk8n4pLzuBQ
214
215
  stoobly_agent/app/cli/types/scenario.py,sha256=28WxmOlbm2Bsek1uu7yc4hJGz-d5oHbYAro7LlFWRoc,81
215
216
  stoobly_agent/app/cli/types/snapshot_migration.py,sha256=4_Re46FKjsflcTOO3qhNsbWWmdEU67SFsF-XE_FKG3M,1859
216
217
  stoobly_agent/app/cli/types/test.py,sha256=1c458B7DFBWsEk5Q1CrZ2CUi84YzEzcs-W4qTcudwAk,714
218
+ stoobly_agent/app/cli/validators/scaffold.py,sha256=ERmdjcryf8YmTHNTHyptKjomATpZo-17bPAPb_ps8ao,1388
217
219
  stoobly_agent/app/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
218
220
  stoobly_agent/app/models/adapters/__init__.py,sha256=cEEE--Bvrvk6DAsHx_uPgFhLnZJETP4zSBtWjMqyIKc,233
219
221
  stoobly_agent/app/models/adapters/joined_request_adapter.py,sha256=fSq16n3AAlxi8KJdBESHp3JGio_M9uzMnHbnQU8VI3w,3598
@@ -708,7 +710,7 @@ stoobly_agent/test/app/models/factories/resource/local_db/helpers/log_test.py,sh
708
710
  stoobly_agent/test/app/models/factories/resource/local_db/helpers/tiebreak_scenario_request_test.py,sha256=a1SFLyEyRRLuADvAw6ckQQKORFXvyK1lyrbkaLWx8oU,3399
709
711
  stoobly_agent/test/app/models/factories/resource/local_db/request_adapter_test.py,sha256=Pzq1cBPnP9oSWG-p0c-VoymoHxgp483QmNwmV1b78RA,8453
710
712
  stoobly_agent/test/app/models/factories/resource/local_db/response_adapter_test.py,sha256=9P95EKH5rZGOrmRkRIDlQZqtiLJHk9735og18Ffwpfw,2204
711
- stoobly_agent/test/app/models/schemas/.stoobly/db/VERSION,sha256=MvlkhO5KUUDkZHqygfa1PgGkfddFw7TZAu1qTt_cq-g,5
713
+ stoobly_agent/test/app/models/schemas/.stoobly/db/VERSION,sha256=0vY2lU8oU9tB_UhSABsgfVlYkLE7wf6JMFZoxaKHBS0,6
712
714
  stoobly_agent/test/app/models/schemas/.stoobly/db/stoobly_agent.sqlite3,sha256=ch8gNx6zIelLKQx65gwFx_LRNqUD3EC5xcHZ0ukIQiU,188416
713
715
  stoobly_agent/test/app/models/schemas/.stoobly/settings.yml,sha256=vLwMjweKOdod6tSLtIlyBefPQuNXq9wio4kBaODKtAU,726
714
716
  stoobly_agent/test/app/models/schemas/.stoobly/tmp/options.json,sha256=OTRzarwus48CTrItedXCrgQttJHSEZonEYc7R_knvYg,2212
@@ -749,8 +751,8 @@ stoobly_agent/test/mock_data/scaffold/docker-compose-local-service.yml,sha256=1W
749
751
  stoobly_agent/test/mock_data/scaffold/index.html,sha256=qJwuYajKZ4ihWZrJQ3BNObV5kf1VGnnm_vqlPJzdqLE,258
750
752
  stoobly_agent/test/mock_data/uspto.yaml,sha256=6U5se7C3o-86J4m9xpOk9Npias399f5CbfWzR87WKwE,7835
751
753
  stoobly_agent/test/test_helper.py,sha256=m_oAI7tmRYCNZdKfNqISWhMv3e44tjeYViQ3nTUfnos,1007
752
- stoobly_agent-1.9.5.dist-info/LICENSE,sha256=o93sj12cdoEOsTCjPaPFsw3Xq0SXs3pPcY-9reE2sEw,548
753
- stoobly_agent-1.9.5.dist-info/METADATA,sha256=Ar6dgljQ63YgEG_psjAkNEVUpGuRpudvTxCY4fAebgQ,3087
754
- stoobly_agent-1.9.5.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
755
- stoobly_agent-1.9.5.dist-info/entry_points.txt,sha256=aq5wix5oC8MDQtmyPGU0xaFrsjJg7WH28NmXh2sc3Z8,56
756
- stoobly_agent-1.9.5.dist-info/RECORD,,
754
+ stoobly_agent-1.9.7.dist-info/LICENSE,sha256=o93sj12cdoEOsTCjPaPFsw3Xq0SXs3pPcY-9reE2sEw,548
755
+ stoobly_agent-1.9.7.dist-info/METADATA,sha256=cd9Awg6zQx8UUifbVQnobldsJhRQOXPJWEljsP1I7_0,3087
756
+ stoobly_agent-1.9.7.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
757
+ stoobly_agent-1.9.7.dist-info/entry_points.txt,sha256=aq5wix5oC8MDQtmyPGU0xaFrsjJg7WH28NmXh2sc3Z8,56
758
+ stoobly_agent-1.9.7.dist-info/RECORD,,