stoobly-agent 1.9.4__py3-none-any.whl → 1.9.6__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.4'
2
+ VERSION = '1.9.6'
@@ -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)
@@ -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(service_paths: List[str], no_publish = False):
13
+ def configure_gateway(workflow_name, 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(service_paths: List[str], no_publish = False):
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(service_paths, gateway_base, app_dir_path)
39
+ __with_traefik_config(workflow_name, 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(service_paths: str, compose: dict, app_dir_path: str):
53
+ def __with_traefik_config(workflow_name: 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(service_paths: str, compose: dict, app_dir_path: str):
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, 'traefik.yml')
101
+ traefik_template_relative_path = os.path.join(DATA_DIR_NAME, TMP_DIR_NAME, workflow_name, '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)):
@@ -33,8 +33,9 @@ app_namespace_dir=$(app_data_dir)/docker
33
33
  app_tmp_dir=$(app_data_dir)/tmp
34
34
  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
- exec_namespace=$(shell echo $(context_dir) | (md5 2>/dev/null || md5sum 2>/dev/null || shasum 2>/dev/null) | awk '{print $$1}')
37
- workflow_script=.stoobly/tmp/$(workflow).sh
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
+ workflow_namespace=$(if $(namespace),$(namespace),$(workflow))
38
+ workflow_script=.stoobly/tmp/$(workflow_namespace)/run.sh
38
39
 
39
40
  # Options
40
41
  certs_dir_options=--ca-certs-dir-path $(ca_certs_dir) --certs-dir-path $(certs_dir)
@@ -43,7 +44,7 @@ working_dir_options=--app-dir-path $(app_dir) --context-dir-path $(context_dir)
43
44
 
44
45
  workflow_down_options=--user-id $(USER_ID) $(workflow_down_extra_options)
45
46
  workflow_log_options=$(workflow_log_extra_options)
46
- workflow_run_options=--script-path $(workflow_script) $(workflow_service_options)
47
+ workflow_run_options=--namespace $(workflow_namespace) --script-path $(workflow_script) $(workflow_service_options)
47
48
  workflow_up_options=$(working_dir_options) $(certs_dir_options) --user-id $(USER_ID) $(workflow_up_extra_options)
48
49
 
49
50
  # Commands
@@ -51,8 +52,8 @@ docker_command=docker
51
52
  docker_compose_command=$(docker_command) compose
52
53
  exec_down=$(docker_compose_command) -f "$(exec_docker_compose_file_path)" $(stoobly_exec_options) down
53
54
  exec_env=APP_DIR="$(app_dir)" CA_CERTS_DIR="$(ca_certs_dir)" USER_ID="$(USER_ID)"
54
- exec_up=$(docker_compose_command) -f "$(exec_docker_compose_file_path)" $(stoobly_exec_options) up --remove-orphans
55
- 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)
56
57
 
57
58
  # Build base image
58
59
  stoobly_exec_build=$(docker_command) build $(stoobly_exec_build_args) $(app_namespace_dir)
@@ -71,6 +72,10 @@ stoobly_exec_run_env=$(source_env) && $(exec_env) CONTEXT_DIR="$(app_dir)"
71
72
  # Workflow run
72
73
  workflow_run=$(source_env) && bash "$(app_dir)/$(workflow_script)"
73
74
 
75
+ action/install:
76
+ $(eval action=install)
77
+ action/uninstall:
78
+ $(eval action=uninstall)
74
79
  ca-cert/install: stoobly/install
75
80
  @if [ -z "$$(ls $(ca_certs_dir) 2> /dev/null)" ]; then \
76
81
  read -p "Installing CA certificate is required for $(workflow)ing requests, continue? (y/N) " confirm && \
@@ -84,10 +89,6 @@ ca-cert/install: stoobly/install
84
89
  certs:
85
90
  @export EXEC_COMMAND=.mkcert && \
86
91
  $(stoobly_exec)
87
- command/install:
88
- $(eval COMMAND=install)
89
- command/uninstall:
90
- $(eval COMMAND=uninstall)
91
92
  exec/down:
92
93
  @$(stoobly_exec_env) EXEC_COMMAND=- EXEC_ARGS=- EXEC_OPTIONS=- $(exec_down)
93
94
  nameservers: tmpdir
@@ -179,7 +180,7 @@ workflow/down:
179
180
  $(stoobly_exec_run) && \
180
181
  $(workflow_run)
181
182
  workflow/hostname: stoobly/install
182
- @read -p "Do you want to $(COMMAND) hostname(s) in /etc/hosts? (y/N) " confirm && \
183
+ @read -p "Do you want to $(action) hostname(s) in /etc/hosts? (y/N) " confirm && \
183
184
  if [ "$$confirm" = "y" ] || [ "$$confirm" = "Y" ]; then \
184
185
  CURRENT_VERSION=$$(stoobly-agent --version); \
185
186
  REQUIRED_VERSION="1.4.0"; \
@@ -187,11 +188,11 @@ workflow/hostname: stoobly/install
187
188
  echo "stoobly-agent version $$REQUIRED_VERSION required. Please run: pipx upgrade stoobly-agent"; \
188
189
  exit 1; \
189
190
  fi; \
190
- echo "Running stoobly-agent scaffold hostname $(COMMAND) $(workflow_service_options)"; \
191
- stoobly-agent scaffold hostname $(COMMAND) --app-dir-path $(app_dir) --workflow $(workflow) $(workflow_service_options); \
191
+ echo "Running stoobly-agent scaffold hostname $(action) $(workflow_service_options)"; \
192
+ stoobly-agent scaffold hostname $(action) --app-dir-path $(app_dir) --workflow $(workflow) $(workflow_service_options); \
192
193
  fi
193
- workflow/hostname/install: command/install workflow/hostname
194
- workflow/hostname/uninstall: command/uninstall workflow/hostname
194
+ workflow/hostname/install: action/install workflow/hostname
195
+ workflow/hostname/uninstall: action/uninstall workflow/hostname
195
196
  workflow/logs:
196
197
  @export EXEC_COMMAND=.logs && \
197
198
  export EXEC_OPTIONS="$(workflow_log_options) $(workflow_run_options) $(options)" && \
@@ -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
@@ -37,6 +36,7 @@ from stoobly_agent.config.data_dir import DataDir
37
36
  from stoobly_agent.lib.logger import bcolors, DEBUG, ERROR, INFO, Logger, WARNING
38
37
 
39
38
  from .helpers.print_service import FORMATS, print_services, select_print_options
39
+ from .validators.scaffold import validate_app_name, validate_hostname, validate_namespace, validate_service_name
40
40
 
41
41
  LOG_ID = 'Scaffold'
42
42
 
@@ -87,22 +87,22 @@ def hostname(ctx):
87
87
  help="Scaffold application"
88
88
  )
89
89
  @click.option('--app-dir-path', default=current_working_dir, help='Path to create the app scaffold.')
90
- @click.option('--force', is_flag=True, help='Overwrite maintained scaffolded app files.')
91
90
  @click.option('--network', help='App default network name. Defaults to app name.')
91
+ @click.option('--quiet', is_flag=True, help='Disable log output.')
92
92
  @click.option('--ui-port', default=4200, type=click.IntRange(1, 65535), help='UI service port.')
93
- @click.argument('app_name')
93
+ @click.argument('app_name', callback=validate_app_name)
94
94
  def create(**kwargs):
95
95
  __validate_app_dir(kwargs['app_dir_path'])
96
96
 
97
97
  app = App(kwargs['app_dir_path'], DOCKER_NAMESPACE)
98
98
 
99
- if kwargs['force'] or not os.path.exists(app.scaffold_namespace_path):
100
- if not kwargs['network']:
101
- kwargs['network'] = kwargs['app_name']
99
+ if not kwargs['quiet'] and os.path.exists(app.scaffold_namespace_path):
100
+ print(f"{kwargs['app_dir_path']} already exists, updating scaffold maintained files...")
102
101
 
103
- AppCreateCommand(app, **kwargs).build()
104
- else:
105
- print(f"{kwargs['app_dir_path']} already exists, use option '--force' to continue ")
102
+ if not kwargs['network']:
103
+ kwargs['network'] = kwargs['app_name']
104
+
105
+ AppCreateCommand(app, **kwargs).build()
106
106
 
107
107
  @app.command(
108
108
  help="Scaffold app service certs"
@@ -129,8 +129,7 @@ def mkcert(**kwargs):
129
129
  @click.option('--app-dir-path', default=current_working_dir, help='Path to application directory.')
130
130
  @click.option('--detached', is_flag=True, help='Use isolated and non-persistent context directory.')
131
131
  @click.option('--env', multiple=True, help='Specify an environment variable.')
132
- @click.option('--force', is_flag=True, help='Overwrite maintained scaffolded service files.')
133
- @click.option('--hostname', help='Service hostname.')
132
+ @click.option('--hostname', callback=validate_hostname, help='Service hostname.')
134
133
  @click.option('--port', type=click.IntRange(1, 65535), help='Service port.')
135
134
  @click.option('--priority', default=5, type=click.FloatRange(1.0, 9.0), help='Determines the service run order. Lower values run first.')
136
135
  @click.option('--proxy-mode', help='''
@@ -139,9 +138,10 @@ def mkcert(**kwargs):
139
138
  upstream proxy modes, SPEC is host specification in
140
139
  the form of "http[s]://host[:port]".
141
140
  ''')
141
+ @click.option('--quiet', is_flag=True, help='Disable log output.')
142
142
  @click.option('--scheme', type=click.Choice(['http', 'https']), help='Defaults to https if hostname is set.')
143
143
  @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')
144
+ @click.argument('service_name', callback=validate_service_name)
145
145
  def create(**kwargs):
146
146
  __validate_app_dir(kwargs['app_dir_path'])
147
147
 
@@ -149,19 +149,16 @@ def create(**kwargs):
149
149
  print(f"Error: {kwargs['service_name']} is invalid. It cannot container '/", file=sys.stderr)
150
150
  sys.exit(1)
151
151
 
152
- if kwargs.get('hostname'):
153
- __validate_hostname(kwargs.get('hostname'))
154
-
155
152
  if kwargs.get("proxy_mode"):
156
153
  __validate_proxy_mode(kwargs.get("proxy_mode"))
157
154
 
158
155
  app = App(kwargs['app_dir_path'], DOCKER_NAMESPACE)
159
-
160
156
  service = Service(kwargs['service_name'], app)
161
- if kwargs['force'] or not os.path.exists(service.dir_path):
162
- __scaffold_build(app, **kwargs)
163
- else:
164
- print(f"{service.dir_path} already exists, use option '--force' to continue")
157
+
158
+ if not kwargs['quiet'] and os.path.exists(service.dir_path):
159
+ print(f"{service.dir_path} already exists, updating scaffold maintained files...")
160
+
161
+ __scaffold_build(app, **kwargs)
165
162
 
166
163
  @service.command(
167
164
  help="List services",
@@ -214,7 +211,7 @@ def delete(**kwargs):
214
211
  help="Update a service config"
215
212
  )
216
213
  @click.option('--app-dir-path', default=current_working_dir, help='Path to application directory.')
217
- @click.option('--hostname', help='Service hostname.')
214
+ @click.option('--hostname', callback=validate_hostname, help='Service hostname.')
218
215
  @click.option('--port', type=click.IntRange(1, 65535), help='Service port.')
219
216
  @click.option('--priority', default=5, type=click.FloatRange(1.0, 9.0), help='Determines the service run order. Lower values run first.')
220
217
  @click.option('--scheme', type=click.Choice(['http', 'https']), help='Defaults to https if hostname is set.')
@@ -237,8 +234,6 @@ def update(**kwargs):
237
234
  service_config = ServiceConfig(service.dir_path)
238
235
 
239
236
  if kwargs['hostname']:
240
- __validate_hostname(kwargs['hostname'])
241
-
242
237
  old_hostname = service_config.hostname
243
238
 
244
239
  if old_hostname != kwargs['hostname']:
@@ -272,7 +267,7 @@ def update(**kwargs):
272
267
  )
273
268
  @click.option('--app-dir-path', default=current_working_dir, help='Path to application directory.')
274
269
  @click.option('--context-dir-path', default=data_dir.context_dir_path, help='Path to Stoobly data directory.')
275
- @click.option('--force', is_flag=True, help='Overwrite maintained scaffolded workflow files.')
270
+ @click.option('--quiet', is_flag=True, help='Disable log output.')
276
271
  @click.option('--service', multiple=True, help='Specify the service(s) to create the workflow for.')
277
272
  @click.option('--template', required=True, type=click.Choice([WORKFLOW_MOCK_TYPE, WORKFLOW_RECORD_TYPE, WORKFLOW_TEST_TYPE]), help='Select which workflow to use as a template.')
278
273
  @click.argument('workflow_name')
@@ -290,10 +285,11 @@ def create(**kwargs):
290
285
  __validate_service_dir(service.dir_path)
291
286
 
292
287
  workflow_dir_path = service.workflow_dir_path(kwargs['workflow_name'])
293
- if kwargs['force'] or not os.path.exists(workflow_dir_path):
294
- __workflow_create(app, **config)
295
- else:
296
- print(f"{workflow_dir_path} already exists, use option '--force' to continue")
288
+
289
+ if not kwargs['quiet'] and os.path.exists(workflow_dir_path):
290
+ print(f"{workflow_dir_path} already exists, updating scaffold maintained files...")
291
+
292
+ __workflow_create(app, **config)
297
293
 
298
294
  @workflow.command(
299
295
  help="Copy a workflow for service(s)",
@@ -328,7 +324,7 @@ def copy(**kwargs):
328
324
  @click.option('--log-level', default=INFO, type=click.Choice([DEBUG, INFO, WARNING, ERROR]), help='''
329
325
  Log levels can be "debug", "info", "warning", or "error"
330
326
  ''')
331
- @click.option('--namespace', help='Workflow namespace.')
327
+ @click.option('--namespace', callback=validate_namespace, help='Workflow namespace.')
332
328
  @click.option('--network', help='Workflow network name.')
333
329
  @click.option('--rmi', is_flag=True, help='Remove images used by containers.')
334
330
  @click.option('--script-path', help='Path to intermediate script path.')
@@ -411,7 +407,7 @@ def down(**kwargs):
411
407
  @click.option('--log-level', default=INFO, type=click.Choice([DEBUG, INFO, WARNING, ERROR]), help='''
412
408
  Log levels can be "debug", "info", "warning", or "error"
413
409
  ''')
414
- @click.option('--namespace', help='Workflow namespace.')
410
+ @click.option('--namespace', callback=validate_namespace, help='Workflow namespace.')
415
411
  @click.option('--script-path', help='Path to intermediate script path.')
416
412
  @click.option('--service', multiple=True, help='Select which services to log. Defaults to all.')
417
413
  @click.argument('workflow_name')
@@ -473,7 +469,7 @@ def logs(**kwargs):
473
469
  Log levels can be "debug", "info", "warning", or "error"
474
470
  ''')
475
471
  @click.option('--mkcert', is_flag=True, help='Set to generate SSL certs for HTTPS services.')
476
- @click.option('--namespace', help='Workflow namespace.')
472
+ @click.option('--namespace', callback=validate_namespace, help='Workflow namespace.')
477
473
  @click.option('--network', help='Workflow network name.')
478
474
  @click.option('--no-build', is_flag=True, help='Do not build images before starting containers.')
479
475
  @click.option('--no-publish', is_flag=True, help='Do not publish all ports.')
@@ -509,7 +505,7 @@ def up(**kwargs):
509
505
 
510
506
  # Gateway ports are dynamically set depending on the workflow run
511
507
  workflow = Workflow(kwargs['workflow_name'], app)
512
- configure_gateway(workflow.service_paths_from_services(services), kwargs['no_publish'])
508
+ configure_gateway(workflow.workflow_name, workflow.service_paths_from_services(services), kwargs['no_publish'])
513
509
 
514
510
  commands: List[WorkflowRunCommand] = []
515
511
  for service in services:
@@ -675,12 +671,12 @@ scaffold.add_command(hostname)
675
671
  def __build_script(**kwargs):
676
672
  script_path = kwargs['script_path']
677
673
  if not script_path:
678
- script_file_name = f"{kwargs['workflow_name']}.sh"
679
- script_path = os.path.join(data_dir.tmp_dir_path, script_file_name)
680
- else:
681
- script_dir = os.path.dirname(script_path)
682
- if not os.path.exists(script_dir):
683
- os.makedirs(script_dir, exist_ok=True)
674
+ script_file_name = 'run.sh'
675
+ script_path = os.path.join(data_dir.tmp_dir_path, kwargs.get('namespace') or kwargs['workflow_name'] or '', script_file_name)
676
+
677
+ script_dir = os.path.dirname(script_path)
678
+ if not os.path.exists(script_dir):
679
+ os.makedirs(script_dir, exist_ok=True)
684
680
 
685
681
  # Truncate
686
682
  with open(script_path, 'w'):
@@ -817,12 +813,6 @@ def __validate_proxy_mode(proxy_mode: str) -> None:
817
813
 
818
814
  # TODO: validate SPEC
819
815
 
820
- def __validate_hostname(hostname: str) -> None:
821
- hostname_regex = re.compile(r'^[a-zA-Z0-9.-]+$')
822
- if not re.search(hostname_regex, hostname):
823
- print(f"Error: {hostname} is invalid.", file=sys.stderr)
824
- sys.exit(1)
825
-
826
816
  def __workflow_create(app, **kwargs):
827
817
  command = WorkflowCreateCommand(app, **kwargs)
828
818
 
@@ -161,7 +161,7 @@ def prune(**kwargs):
161
161
  )
162
162
  @click.option('--format', type=click.Choice(FORMATS), help='Format output.')
163
163
  @click.option('--select', multiple=True, help='Select column(s) to display.')
164
- @click.option('--verify', is_flag=True, default=False)
164
+ @click.option('--no-verify', is_flag=True, default=False)
165
165
  @click.option('--without-headers', is_flag=True, default=False, help='Disable printing column headers.')
166
166
  @click.argument('uuid')
167
167
  def update(**kwargs):
@@ -179,7 +179,7 @@ def update(**kwargs):
179
179
  print(f"Error: {kwargs['uuid']} not found", file=sys.stderr)
180
180
  sys.exit(1)
181
181
 
182
- if kwargs['verify']:
182
+ if not kwargs['no_verify']:
183
183
  if event.is_request():
184
184
  snapshot: RequestSnapshot = event.snapshot()
185
185
  __verify_request(snapshot)
@@ -0,0 +1,34 @@
1
+ import re
2
+ import sys
3
+
4
+ def validate_app_name(ctx, param, app_name: str) -> str:
5
+ app_name_regex = re.compile(r'^[a-zA-Z0-9._-]+$')
6
+ if not re.search(app_name_regex, app_name):
7
+ print(f"Error: app name {app_name} is invalid.", file=sys.stderr)
8
+ sys.exit(1)
9
+ return app_name
10
+
11
+ def validate_hostname(ctx, param, hostname: str) -> str:
12
+ if not hostname:
13
+ return
14
+ hostname_regex = re.compile(r'^[a-zA-Z0-9.-]+$')
15
+ if not re.search(hostname_regex, hostname):
16
+ print(f"Error: hostname {hostname} is invalid.", file=sys.stderr)
17
+ sys.exit(1)
18
+ return hostname
19
+
20
+ def validate_namespace(ctx, param, namespace: str) -> str:
21
+ if not namespace:
22
+ return
23
+ namespace_regex = re.compile(r'^[a-z0-9_-]+$')
24
+ if not re.search(namespace_regex, namespace) or namespace[0] in ['-', '_']:
25
+ print(f"Error: namespace {namespace} is invalid.", file=sys.stderr)
26
+ sys.exit(1)
27
+ return namespace
28
+
29
+ def validate_service_name(ctx, param, service_name: str) -> str:
30
+ service_name_regex = re.compile(r'^[a-zA-Z0-9._-]+$')
31
+ if not re.search(service_name_regex, service_name):
32
+ print(f"Error: service name {service_name} is invalid.", file=sys.stderr)
33
+ sys.exit(1)
34
+ return service_name
@@ -15,7 +15,7 @@ class ScaffoldCliInvoker():
15
15
 
16
16
  result = runner.invoke(scaffold, ['app', 'create',
17
17
  '--app-dir-path', app_dir_path,
18
- '--force',
18
+ '--quiet',
19
19
  app_name
20
20
  ])
21
21
 
@@ -45,10 +45,10 @@ class ScaffoldCliInvoker():
45
45
  result = runner.invoke(scaffold, ['service', 'create',
46
46
  '--app-dir-path', app_dir_path,
47
47
  '--env', 'TEST',
48
- '--force',
49
48
  '--hostname', hostname,
50
49
  '--scheme', scheme,
51
50
  '--port', port,
51
+ '--quiet',
52
52
  '--workflow', 'mock',
53
53
  '--workflow', 'record',
54
54
  '--workflow', 'test',
@@ -70,12 +70,12 @@ class ScaffoldCliInvoker():
70
70
 
71
71
  result = runner.invoke(scaffold, ['service', 'create',
72
72
  '--app-dir-path', app_dir_path,
73
- '--force',
74
73
  '--hostname', hostname,
75
74
  '--scheme', scheme,
76
75
  '--port', port,
77
76
  '--proxy-mode', proxy_mode_reverse_spec,
78
77
  '--detached',
78
+ '--quiet',
79
79
  '--workflow', 'test',
80
80
  service_name
81
81
  ])
@@ -94,7 +94,7 @@ class TestUpdate():
94
94
  def updated_event(self, runner: CliRunner, put_event: LogEvent):
95
95
  time.sleep(1) # Simulate update at different time
96
96
 
97
- apply_result = runner.invoke(snapshot, ['update', '--verify', put_event.uuid])
97
+ apply_result = runner.invoke(snapshot, ['update', put_event.uuid])
98
98
  assert apply_result.exit_code == 0
99
99
  log = Log()
100
100
  events = log.events
@@ -1 +1 @@
1
- 1.9.3
1
+ 1.9.6
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: stoobly-agent
3
- Version: 1.9.4
3
+ Version: 1.9.6
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=xHYLozasADB35BWCrRP8wlaWwr4WTbSnYvQyjTdvED4,44
1
+ stoobly_agent/__init__.py,sha256=UlQip9_Ul2fRC4UHN6HFN0lQDoz_AEInAcMSJiQnEow,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
@@ -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=872qX9atoNWMXvSZcnG5cvVXarWShxBS9cJ_Q-YrCW8,3850
85
+ stoobly_agent/app/cli/scaffold/docker/service/configure_gateway.py,sha256=jtx8lhvKHl0ubSHaQpE8m9lCYnOJ6Qju-SBJPBs0l7I,3921
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
@@ -104,7 +104,7 @@ stoobly_agent/app/cli/scaffold/service_workflow.py,sha256=sQ_Edy_wGHKMXpD0DmhnOW
104
104
  stoobly_agent/app/cli/scaffold/service_workflow_validate_command.py,sha256=xONRUtfC3IBd-Kr4wdUKWgx9ppSsbu2H72pb2VinizQ,11412
105
105
  stoobly_agent/app/cli/scaffold/templates/__init__.py,sha256=x8C_a0VoO_vUbosp4_6IC1U7Ge9NnUdVKDPpVMtMkeY,171
106
106
  stoobly_agent/app/cli/scaffold/templates/app/.Dockerfile.context,sha256=Okk4Q0Fj7Wi5NU58gQfpjpFwAL3RUBJyRe56kteQfcA,158
107
- stoobly_agent/app/cli/scaffold/templates/app/.Makefile,sha256=m89dz5LDaG5MXiBdBb9lxi6pAP90JOipNwS_EK9kGOs,9124
107
+ stoobly_agent/app/cli/scaffold/templates/app/.Makefile,sha256=4CTrKRHho9fl7NOGrQM0lJTfn-X09ddQKBq9Zsbjbww,9278
108
108
  stoobly_agent/app/cli/scaffold/templates/app/.docker-compose.base.yml,sha256=6tFqXh3ine8vaD0FCL5TMoY5NjKx2wLUR8XpW3tJtew,245
109
109
  stoobly_agent/app/cli/scaffold/templates/app/.docker-compose.networks.yml,sha256=I4PbJpQjFHb5IbAUWNvYM6okDEtmwtKFDQg-yog05WM,141
110
110
  stoobly_agent/app/cli/scaffold/templates/app/Makefile,sha256=TEmPG7Bf0KZOnmfsgdzza3UdwcVMmM5Lj1YdLc4cgjA,79
@@ -203,9 +203,9 @@ stoobly_agent/app/cli/scaffold/workflow_env.py,sha256=x8V5pJmIiklD3f2q2-qq-CORf4
203
203
  stoobly_agent/app/cli/scaffold/workflow_log_command.py,sha256=Bke4lMOMxuDUFuAx9nlXHbKgYMO4KAg9ASHvjz4aVWc,1372
204
204
  stoobly_agent/app/cli/scaffold/workflow_run_command.py,sha256=eF3aaK4OIZXYuSBEAeBnhAL7EZrS1G4mSYrJbEiXt2o,11082
205
205
  stoobly_agent/app/cli/scaffold/workflow_validate_command.py,sha256=Uo_yo6rVR1ZR7xpvsQvlH48AyMBVLRupd4G-bRjzm_Q,5584
206
- stoobly_agent/app/cli/scaffold_cli.py,sha256=huVZUpBUW6sfcNmhAlcQTKYVCCg0kHhcjezxt_Fm4sk,32043
206
+ stoobly_agent/app/cli/scaffold_cli.py,sha256=9NOSl3Qy7LN3PphuOqTFEM6t0PanVEHow8iyW-5eTW0,31958
207
207
  stoobly_agent/app/cli/scenario_cli.py,sha256=3J1EiJOvunkfWrEkOsanw-XrKkOk78ij_GjBlE9p7CE,8229
208
- stoobly_agent/app/cli/snapshot_cli.py,sha256=Uf6g6ivsD0hUY8G99eU0fxzS3FymncAhI70PxV7Uaac,11919
208
+ stoobly_agent/app/cli/snapshot_cli.py,sha256=1Dw5JgDlmG6vctrawIRO7CdB73vAQk_wRBnPG2lVOrQ,11929
209
209
  stoobly_agent/app/cli/trace_cli.py,sha256=K7E-vx3JUcqEDSWOdIOi_AieKNQz7dBfmRrVvKDkzFI,4605
210
210
  stoobly_agent/app/cli/types/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
211
211
  stoobly_agent/app/cli/types/output.py,sha256=2wazv56g5IwLQeJCWfJXXAxTB9Y5WH1cKMHbpjbXNeo,439
@@ -214,6 +214,7 @@ stoobly_agent/app/cli/types/request.py,sha256=QthojE5sfx7OvKu-vVNnSUfGk8n4pLzuBQ
214
214
  stoobly_agent/app/cli/types/scenario.py,sha256=28WxmOlbm2Bsek1uu7yc4hJGz-d5oHbYAro7LlFWRoc,81
215
215
  stoobly_agent/app/cli/types/snapshot_migration.py,sha256=4_Re46FKjsflcTOO3qhNsbWWmdEU67SFsF-XE_FKG3M,1859
216
216
  stoobly_agent/app/cli/types/test.py,sha256=1c458B7DFBWsEk5Q1CrZ2CUi84YzEzcs-W4qTcudwAk,714
217
+ stoobly_agent/app/cli/validators/scaffold.py,sha256=mjLdb1lO8_SKOspwQSONSA8upXANg6HOvtttkHEqhrk,1187
217
218
  stoobly_agent/app/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
218
219
  stoobly_agent/app/models/adapters/__init__.py,sha256=cEEE--Bvrvk6DAsHx_uPgFhLnZJETP4zSBtWjMqyIKc,233
219
220
  stoobly_agent/app/models/adapters/joined_request_adapter.py,sha256=fSq16n3AAlxi8KJdBESHp3JGio_M9uzMnHbnQU8VI3w,3598
@@ -677,7 +678,7 @@ stoobly_agent/test/app/cli/request/request_reset_test.py,sha256=5My6Z452eideAOUu
677
678
  stoobly_agent/test/app/cli/request/request_response_test.py,sha256=Fu-A8tIn016DKme4WIaPzo3YeFY-CPtTOpaSFigUVVM,1263
678
679
  stoobly_agent/test/app/cli/request/request_snapshot_test.py,sha256=3kMmv0CuvnMXLgDQA-_u9S1DIiNOdL63L-IptVuOpf8,6308
679
680
  stoobly_agent/test/app/cli/request/request_test_test.py,sha256=-cJNXKjgryVVfVt-7IN5fIhBwe3NjFoPmeavDH8lAjU,5527
680
- stoobly_agent/test/app/cli/scaffold/cli_invoker.py,sha256=_nGDLUsYxqkeqs5DdhvAeXy3IuotpgqKHXKVzu6GDF4,3700
681
+ stoobly_agent/test/app/cli/scaffold/cli_invoker.py,sha256=ZOFeLh9TejJ_k3gQ0KC6iIuqKhwrkqZV5U7CMdRDnwM,3700
681
682
  stoobly_agent/test/app/cli/scaffold/cli_test.py,sha256=sMNvO845MIu5DVGa1HmwXQDmKDcwrfNTdEb3fK5886w,4557
682
683
  stoobly_agent/test/app/cli/scaffold/e2e_test.py,sha256=IGWT0EXrMtB8i8kdLFbN7O8NvLrJYTi-iQ_GRiUoA94,12978
683
684
  stoobly_agent/test/app/cli/scaffold/hosts_file_manager_test.py,sha256=ztcPh1x0ZCW1FWA5YL4ulEVjfbW9TOPgk1bnSDPNmCw,2287
@@ -692,7 +693,7 @@ stoobly_agent/test/app/cli/snapshot/snapshot_apply_test.py,sha256=mpkTZx8eaFFZU_
692
693
  stoobly_agent/test/app/cli/snapshot/snapshot_copy_test.py,sha256=Yg78-FhSiG_r6Jpm-sN8sn0LjVXTwTOXt6hg8ni2GIY,1953
693
694
  stoobly_agent/test/app/cli/snapshot/snapshot_migrate_test.py,sha256=voEvblK6CMGCrSJDTHVmkUkLXj0auNb78jxlGiiBBQQ,7370
694
695
  stoobly_agent/test/app/cli/snapshot/snapshot_prune_test.py,sha256=bn4yUU7Eb4-6GnwnRaPZPi5Cn7XEaIsrJ_mB7jydgWw,6693
695
- stoobly_agent/test/app/cli/snapshot/snapshot_update_test.py,sha256=fILsX2M5j4wuLRP6LJTHe4CPB8gvaEbsSoYmFCHmKVk,4514
696
+ stoobly_agent/test/app/cli/snapshot/snapshot_update_test.py,sha256=yx_QDPYc5utiqlRhy3hTGBKLafGq9nC_lDV-0k0qWOo,4502
696
697
  stoobly_agent/test/app/models/adapters/joined_rquest_adapter_test.py,sha256=bF7WMrAiASQDNzDTvIXGJhsWLNhfYOmdQpSDo0hyWYY,1098
697
698
  stoobly_agent/test/app/models/adapters/orm/joined_request_string_adapter_test.py,sha256=a2IHTk3l7aiLyYF7vtqissrk0MFTF2wlUBiaKWyJKfU,2667
698
699
  stoobly_agent/test/app/models/adapters/orm/request/orm_mitmproxy_request_adapter_test.py,sha256=PbJsAaxPUEbF9vM7DX4z858biWf4qlGnvE8KBuy8SgY,2763
@@ -708,7 +709,7 @@ stoobly_agent/test/app/models/factories/resource/local_db/helpers/log_test.py,sh
708
709
  stoobly_agent/test/app/models/factories/resource/local_db/helpers/tiebreak_scenario_request_test.py,sha256=a1SFLyEyRRLuADvAw6ckQQKORFXvyK1lyrbkaLWx8oU,3399
709
710
  stoobly_agent/test/app/models/factories/resource/local_db/request_adapter_test.py,sha256=Pzq1cBPnP9oSWG-p0c-VoymoHxgp483QmNwmV1b78RA,8453
710
711
  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=JyvJHgCQJn-THkr7jL9H-s1Ea2VWcQz-X3BiHK64uxk,6
712
+ stoobly_agent/test/app/models/schemas/.stoobly/db/VERSION,sha256=0vY2lU8oU9tB_UhSABsgfVlYkLE7wf6JMFZoxaKHBS0,6
712
713
  stoobly_agent/test/app/models/schemas/.stoobly/db/stoobly_agent.sqlite3,sha256=ch8gNx6zIelLKQx65gwFx_LRNqUD3EC5xcHZ0ukIQiU,188416
713
714
  stoobly_agent/test/app/models/schemas/.stoobly/settings.yml,sha256=vLwMjweKOdod6tSLtIlyBefPQuNXq9wio4kBaODKtAU,726
714
715
  stoobly_agent/test/app/models/schemas/.stoobly/tmp/options.json,sha256=OTRzarwus48CTrItedXCrgQttJHSEZonEYc7R_knvYg,2212
@@ -749,8 +750,8 @@ stoobly_agent/test/mock_data/scaffold/docker-compose-local-service.yml,sha256=1W
749
750
  stoobly_agent/test/mock_data/scaffold/index.html,sha256=qJwuYajKZ4ihWZrJQ3BNObV5kf1VGnnm_vqlPJzdqLE,258
750
751
  stoobly_agent/test/mock_data/uspto.yaml,sha256=6U5se7C3o-86J4m9xpOk9Npias399f5CbfWzR87WKwE,7835
751
752
  stoobly_agent/test/test_helper.py,sha256=m_oAI7tmRYCNZdKfNqISWhMv3e44tjeYViQ3nTUfnos,1007
752
- stoobly_agent-1.9.4.dist-info/LICENSE,sha256=o93sj12cdoEOsTCjPaPFsw3Xq0SXs3pPcY-9reE2sEw,548
753
- stoobly_agent-1.9.4.dist-info/METADATA,sha256=xDOjRS-p08haoG5tLVE1xI3X2qrM6gaplT5nmlIN8s8,3087
754
- stoobly_agent-1.9.4.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
755
- stoobly_agent-1.9.4.dist-info/entry_points.txt,sha256=aq5wix5oC8MDQtmyPGU0xaFrsjJg7WH28NmXh2sc3Z8,56
756
- stoobly_agent-1.9.4.dist-info/RECORD,,
753
+ stoobly_agent-1.9.6.dist-info/LICENSE,sha256=o93sj12cdoEOsTCjPaPFsw3Xq0SXs3pPcY-9reE2sEw,548
754
+ stoobly_agent-1.9.6.dist-info/METADATA,sha256=uLLTaIvuC4eHvFOImTS-phj_G1HeK2F0qjhOYpPiyrM,3087
755
+ stoobly_agent-1.9.6.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
756
+ stoobly_agent-1.9.6.dist-info/entry_points.txt,sha256=aq5wix5oC8MDQtmyPGU0xaFrsjJg7WH28NmXh2sc3Z8,56
757
+ stoobly_agent-1.9.6.dist-info/RECORD,,