Skip to content

Commit aaa3f26

Browse files
J3utterJ3utter
andauthored
Remove scaffold workflow logs --container option (#721)
Co-authored-by: J3utter <j3utter@gmail.com>
1 parent 2cd270f commit aaa3f26

4 files changed

Lines changed: 171 additions & 32 deletions

File tree

stoobly_agent/app/cli/scaffold/docker/workflow/run_command.py

Lines changed: 26 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@
77
from typing import List
88
from types import FunctionType
99

10-
from stoobly_agent.app.cli.scaffold.constants import PROXY_MODE_REVERSE, WORKFLOW_CONTAINER_PROXY, WORKFLOW_CONTAINER_SERVICE, WORKFLOW_NAME
10+
from stoobly_agent.app.cli.scaffold.constants import PROXY_MODE_REVERSE, WORKFLOW_CONTAINER_INIT, WORKFLOW_CONTAINER_PROXY, WORKFLOW_CONTAINER_SERVICE, WORKFLOW_NAME
1111
from stoobly_agent.app.cli.scaffold.docker.constants import APP_EGRESS_NETWORK_TEMPLATE, APP_INGRESS_NETWORK_TEMPLATE, DOCKERFILE_CONTEXT
1212
from stoobly_agent.app.cli.scaffold.docker.service.gateway_base import GatewayBase
13-
from stoobly_agent.app.cli.scaffold.templates.constants import CORE_ENTRYPOINT_SERVICE_NAME, CORE_GATEWAY_SERVICE_NAME, CORE_SERVICES_DOCKER
13+
from stoobly_agent.app.cli.scaffold.templates.constants import CORE_BUILD_SERVICE_NAME, CORE_ENTRYPOINT_SERVICE_NAME, CORE_GATEWAY_SERVICE_NAME, CORE_MOCK_UI_SERVICE_NAME, CORE_SERVICES_DOCKER
1414
from stoobly_agent.app.cli.scaffold.workflow import Workflow
1515
from stoobly_agent.app.cli.scaffold.workflow_run_command import WorkflowRunCommand
1616
from stoobly_agent.app.cli.types.workflow_run_command import BuildOptions, DownOptions, UpOptions, WorkflowDownOptions, WorkflowUpOptions, WorkflowLogsOptions
@@ -205,18 +205,20 @@ def logs(self, **options: WorkflowLogsOptions):
205205
self.__find_and_verify_timestamp_file()
206206

207207
print_service_header = options.get('print_service_header')
208+
209+
if self.app_config.proxy_mode == PROXY_MODE_REVERSE:
210+
containers_to_log = [WORKFLOW_CONTAINER_INIT, WORKFLOW_CONTAINER_PROXY]
211+
else:
212+
containers_to_log = [WORKFLOW_CONTAINER_INIT, WORKFLOW_CONTAINER_SERVICE]
208213

209214
# Filter services based on options
210215
filtered_services = []
211216

212217
if self.app_config.proxy_mode == PROXY_MODE_REVERSE:
213-
if len(options.get('container', [])) == 0:
214-
options['container'] = [WORKFLOW_CONTAINER_PROXY]
215-
216218
for service in self.services:
217219
if len(options.get('service', [])) == 0:
218220
# If no filter is specified, ignore CORE_SERVICES
219-
if service in CORE_SERVICES_DOCKER:
221+
if service == CORE_BUILD_SERVICE_NAME or service == CORE_MOCK_UI_SERVICE_NAME or service == CORE_GATEWAY_SERVICE_NAME:
220222
continue
221223
else:
222224
# If a filter is specified, ignore all other services
@@ -225,13 +227,10 @@ def logs(self, **options: WorkflowLogsOptions):
225227

226228
filtered_services.append(service)
227229
else:
228-
if len(options.get('container', [])) == 0:
229-
options['container'] = [WORKFLOW_CONTAINER_SERVICE]
230-
231230
for service in self.services:
232231
if len(options.get('service', [])) == 0:
233-
# If no filter is specified, ignore all other services except the gateway
234-
if service != CORE_GATEWAY_SERVICE_NAME:
232+
# If no filter is specified, ignore CORE_SERVICES other than the gateway
233+
if service == CORE_BUILD_SERVICE_NAME or service == CORE_MOCK_UI_SERVICE_NAME:
235234
continue
236235
else:
237236
# If a filter is specified, ignore all other services
@@ -249,15 +248,26 @@ def logs(self, **options: WorkflowLogsOptions):
249248
commands.append((service, command))
250249

251250
# Sort commands by priority and execute
252-
commands = sorted(commands, key=lambda x: x[1].service_config.priority)
251+
if self.app_config.proxy_mode == PROXY_MODE_REVERSE:
252+
commands = sorted(commands, key=lambda x: x[1].service_config.priority)
253+
else:
254+
# For forward proxy, gateway is second to last; entrypoint is last (follow target)
255+
commands = sorted(
256+
commands,
257+
key=lambda x: (
258+
2 if x[0] == CORE_ENTRYPOINT_SERVICE_NAME else 1 if x[0] == CORE_GATEWAY_SERVICE_NAME else 0,
259+
x[1].service_config.priority,
260+
),
261+
)
262+
253263
for index, (service, command) in enumerate(commands):
254264
if print_service_header:
255265
print_service_header(service)
256266

257267
follow = options.get('follow', False) and index == len(commands) - 1
258268
shell_commands = self._build_log_commands(
259269
command,
260-
containers=options.get('container', []),
270+
containers=containers_to_log,
261271
follow=follow,
262272
namespace=options.get('namespace')
263273
)
@@ -279,15 +289,13 @@ def _build_log_commands(self, command, containers=None, follow=False, namespace=
279289
), containers or []
280290
)
281291
)
292+
matched = [c for c in allowed_containers if c in available_containers]
282293

283-
for index, container in enumerate(available_containers):
284-
if container not in allowed_containers:
285-
continue
286-
294+
for index, container in enumerate(matched):
287295
container_name = self._container_name(container, namespace or command.workflow_name)
288296
log_commands.append(f"echo \"=== Logging {container_name}\"")
289297

290-
if follow and index == len(available_containers) - 1:
298+
if follow and index == len(matched) - 1:
291299
docker_command = ['docker', 'logs', '--follow', container_name]
292300
else:
293301
docker_command = ['docker', 'logs', container_name]

stoobly_agent/app/cli/scaffold_cli.py

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -404,7 +404,7 @@ def show(**kwargs):
404404
)
405405
@click.option('--app-dir-path', default=context_dir_path, help='Path to application directory.')
406406
@click.option('--context-dir-path', default=data_dir.context_dir_path, help='Path to Stoobly data directory.')
407-
@click.option('--containerized', is_flag=True, help='Set if run from within a container.')
407+
@click.option('--containerized', is_flag=True, hidden=True, help='Set if run from within a container.')
408408
@click.option('--dry-run', default=False, is_flag=True)
409409
@click.option('--hostname-uninstall-confirm', default=None, type=click.Choice(['y', 'Y', 'n', 'N']), help='Confirm answer to hostname uninstall prompt.')
410410
@click.option('--log-level', default=INFO, type=click.Choice([DEBUG, INFO, WARNING, ERROR]), help='''
@@ -521,7 +521,7 @@ def down(**kwargs):
521521

522522
finally:
523523
# Execute the workflow down
524-
command_args = { 'print_service_header': lambda service_name: __print_header(f"SERVICE {service_name}") }
524+
command_args = { 'print_service_header': lambda service_name: __print_header(f"service {service_name}") }
525525
workflow_command.down(
526526
**command_args,
527527
**kwargs
@@ -535,12 +535,9 @@ def down(**kwargs):
535535
help="Show or follow logs from workflow service(s)",
536536
)
537537
@click.option('--app-dir-path', default=context_dir_path, help='Path to application directory.')
538-
@click.option(
539-
'--container', multiple=True, help=f"Select which containers to log."
540-
)
541-
@click.option('--containerized', is_flag=True, help='Set if run from within a container.')
538+
@click.option('--containerized', is_flag=True, hidden=True, help='Set if run from within a container.')
542539
@click.option('--dry-run', default=False, is_flag=True, help='If set, prints commands.')
543-
@click.option('--follow', is_flag=True, help='Follow last container log output.')
540+
@click.option('--follow', is_flag=True, help='Follow log output.')
544541
@click.option('--log-level', default=INFO, type=click.Choice([DEBUG, INFO, WARNING, ERROR]), help='''
545542
Log levels can be "debug", "info", "warning", or "error"
546543
''')
@@ -583,7 +580,7 @@ def logs(**kwargs):
583580
)
584581

585582
# Execute the workflow logs
586-
command_args = { 'print_service_header': lambda service_name: __print_header(f"SERVICE {service_name}") }
583+
command_args = { 'print_service_header': lambda service_name: __print_header(f"service {service_name}") }
587584
workflow_command.logs(
588585
**command_args,
589586
**kwargs
@@ -596,7 +593,7 @@ def logs(**kwargs):
596593
@click.option('--ca-certs-dir-path', default=None, help='Path to ca certs directory used to sign SSL certs. Defaults to the ca_certs dir of the context.')
597594
@click.option('--ca-certs-install-confirm', default=None, type=click.Choice(['y', 'Y', 'n', 'N']), help='Confirm answer to CA certificate installation prompt.')
598595
@click.option('--certs-dir-path', help='Path to certs directory. Defaults to the certs dir of the context.')
599-
@click.option('--containerized', is_flag=True, help='Set if run from within a container.')
596+
@click.option('--containerized', is_flag=True, hidden=True, help='Set if run from within a container.')
600597
@click.option('--context-dir-path', default=data_dir.context_dir_path, help='Path to Stoobly data directory.')
601598
@click.option('--detached', is_flag=True, help='If set, will run the highest priority service in the background.')
602599
@click.option('--dry-run', default=False, is_flag=True, help='If set, prints commands instead of running them.')
@@ -705,7 +702,7 @@ def up(**kwargs):
705702
Logger.instance(LOG_ID).info(f"To view logs, run `stoobly-agent scaffold workflow logs{options_str} {kwargs['workflow_name']}`")
706703

707704
# Execute the workflow
708-
command_args = { 'print_service_header': lambda service_name: __print_header(f"SERVICE {service_name}") }
705+
command_args = { 'print_service_header': lambda service_name: __print_header(f"service {service_name}") }
709706
workflow_command.up(
710707
**command_args,
711708
**kwargs
@@ -717,7 +714,7 @@ def up(**kwargs):
717714
@click.option('--app-dir-path', default=context_dir_path, help='Path to application directory.')
718715
@click.option('--ca-certs-dir-path', default=None, help='Path to ca certs directory used to sign SSL certs. Defaults to the ca_certs dir of the context.')
719716
@click.option('--certs-dir-path', help='Path to certs directory. Defaults to the certs dir of the context.')
720-
@click.option('--containerized', is_flag=True, help='Set if run from within a container.')
717+
@click.option('--containerized', is_flag=True, hidden=True, help='Set if run from within a container.')
721718
@click.option('--context-dir-path', default=data_dir.context_dir_path, help='Path to Stoobly data directory.')
722719
@click.option('--service', multiple=True, help='Select specific services. Defaults to all.')
723720
@click.argument('workflow_name')
@@ -736,7 +733,7 @@ def mkcert(**kwargs):
736733
help="Sync normalize rewrite rules from service upstream hostname, port, and scheme"
737734
)
738735
@click.option('--app-dir-path', default=context_dir_path, help='Path to application directory.')
739-
@click.option('--containerized', is_flag=True, help='Set if run from within a container.')
736+
@click.option('--containerized', is_flag=True, hidden=True, help='Set if run from within a container.')
740737
@click.option('--context-dir-path', default=data_dir.context_dir_path, help='Path to Stoobly data directory.')
741738
@click.option('--service', multiple=True, help='Select specific services. Defaults to all.')
742739
@click.argument('workflow_name')
@@ -756,7 +753,7 @@ def rewrite(**kwargs):
756753
help="Configure include filter rules for workflow service(s)"
757754
)
758755
@click.option('--app-dir-path', default=context_dir_path, help='Path to application directory.')
759-
@click.option('--containerized', is_flag=True, help='Set if run from within a container.')
756+
@click.option('--containerized', is_flag=True, hidden=True, help='Set if run from within a container.')
760757
@click.option('--context-dir-path', default=data_dir.context_dir_path, help='Path to Stoobly data directory.')
761758
@click.option('--service', multiple=True, help='Select specific services. Defaults to all.')
762759
@click.argument('workflow_name')

stoobly_agent/app/cli/types/workflow_run_command.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,6 @@ class WorkflowUpOptions(TypedDict, total=False):
5656

5757
class WorkflowLogsOptions(TypedDict, total=False):
5858
print_service_header: Optional[Callable[[str], None]]
59-
container: List[str]
6059
follow: bool
6160
namespace: Optional[str]
6261
# CLI-specific options that get passed through
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
import pytest
2+
3+
from unittest.mock import MagicMock
4+
5+
from stoobly_agent.app.cli.scaffold.constants import (
6+
WORKFLOW_CONTAINER_INIT,
7+
WORKFLOW_CONTAINER_PROXY,
8+
WORKFLOW_CONTAINER_SERVICE,
9+
)
10+
from stoobly_agent.app.cli.scaffold.templates.constants import CORE_ENTRYPOINT_SERVICE_NAME, CORE_GATEWAY_SERVICE_NAME
11+
from stoobly_agent.app.cli.scaffold.docker.workflow.run_command import DockerWorkflowRunCommand
12+
13+
14+
@pytest.fixture
15+
def run_command():
16+
return DockerWorkflowRunCommand.__new__(DockerWorkflowRunCommand)
17+
18+
19+
def _stub_command(service_name, workflow_name, compose_services):
20+
command = MagicMock()
21+
command.service_name = service_name
22+
command.workflow_name = workflow_name
23+
command.containers = {name: {} for name in compose_services}
24+
return command
25+
26+
27+
class TestBuildLogCommands:
28+
29+
def test_reverse_logs_init_then_proxy(self, run_command):
30+
command = _stub_command('api', 'record', ['api.init', 'api.proxy'])
31+
log_commands = run_command._build_log_commands(
32+
command,
33+
containers=[WORKFLOW_CONTAINER_INIT, WORKFLOW_CONTAINER_PROXY],
34+
namespace='record',
35+
)
36+
37+
assert log_commands == [
38+
'echo "=== Logging record-api.init-1"',
39+
'docker logs record-api.init-1',
40+
'echo "=== Logging record-api.proxy-1"',
41+
'docker logs record-api.proxy-1',
42+
]
43+
44+
def test_reverse_follow_only_on_proxy(self, run_command):
45+
command = _stub_command('api', 'record', ['api.init', 'api.proxy'])
46+
log_commands = run_command._build_log_commands(
47+
command,
48+
containers=[WORKFLOW_CONTAINER_INIT, WORKFLOW_CONTAINER_PROXY],
49+
follow=True,
50+
namespace='record',
51+
)
52+
53+
assert log_commands[1] == 'docker logs record-api.init-1'
54+
assert log_commands[3] == 'docker logs --follow record-api.proxy-1'
55+
56+
def test_forward_logs_service_when_init_absent(self, run_command):
57+
command = _stub_command('gateway', 'record', ['gateway.service'])
58+
log_commands = run_command._build_log_commands(
59+
command,
60+
containers=[WORKFLOW_CONTAINER_INIT, WORKFLOW_CONTAINER_SERVICE],
61+
namespace='record',
62+
)
63+
64+
assert log_commands == [
65+
'echo "=== Logging record-gateway.service-1"',
66+
'docker logs record-gateway.service-1',
67+
]
68+
69+
def test_forward_follow_only_on_service(self, run_command):
70+
command = _stub_command('gateway', 'record', ['gateway.init', 'gateway.service'])
71+
log_commands = run_command._build_log_commands(
72+
command,
73+
containers=[WORKFLOW_CONTAINER_INIT, WORKFLOW_CONTAINER_SERVICE],
74+
follow=True,
75+
namespace='record',
76+
)
77+
78+
assert log_commands[1] == 'docker logs record-gateway.init-1'
79+
assert log_commands[3] == 'docker logs --follow record-gateway.service-1'
80+
81+
def test_skips_missing_init_container(self, run_command):
82+
command = _stub_command('api', 'record', ['api.proxy'])
83+
log_commands = run_command._build_log_commands(
84+
command,
85+
containers=[WORKFLOW_CONTAINER_INIT, WORKFLOW_CONTAINER_PROXY],
86+
namespace='record',
87+
)
88+
89+
assert log_commands == [
90+
'echo "=== Logging record-api.proxy-1"',
91+
'docker logs record-api.proxy-1',
92+
]
93+
94+
95+
class TestForwardLogCommandSort:
96+
97+
def _sort(self, commands):
98+
return sorted(
99+
commands,
100+
key=lambda x: (
101+
2 if x[0] == CORE_ENTRYPOINT_SERVICE_NAME else 1 if x[0] == CORE_GATEWAY_SERVICE_NAME else 0,
102+
x[1].service_config.priority,
103+
),
104+
)
105+
106+
def test_gateway_sorted_second_to_last(self):
107+
api_command = MagicMock()
108+
api_command.service_config.priority = 10
109+
gateway_command = MagicMock()
110+
gateway_command.service_config.priority = 0
111+
entrypoint_command = MagicMock()
112+
entrypoint_command.service_config.priority = 5
113+
114+
commands = [
115+
('gateway', gateway_command),
116+
('entrypoint', entrypoint_command),
117+
('api', api_command),
118+
]
119+
sorted_commands = self._sort(commands)
120+
121+
assert [service for service, _ in sorted_commands] == ['api', 'gateway', 'entrypoint']
122+
123+
def test_gateway_last_when_entrypoint_absent(self):
124+
api_command = MagicMock()
125+
api_command.service_config.priority = 10
126+
gateway_command = MagicMock()
127+
gateway_command.service_config.priority = 0
128+
129+
commands = [
130+
('gateway', gateway_command),
131+
('api', api_command),
132+
]
133+
sorted_commands = self._sort(commands)
134+
135+
assert [service for service, _ in sorted_commands] == ['api', 'gateway']

0 commit comments

Comments
 (0)