zrb 0.26.2__py3-none-any.whl → 0.28.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 (31) hide show
  1. zrb/__init__.py +0 -3
  2. zrb/action/runner.py +0 -2
  3. zrb/builtin/devtool/install/zsh/install.sh +1 -1
  4. zrb/builtin/project/add/fastapp/app/template/_automate/snake_zrb_app_name/container/microservices/start.py +7 -2
  5. zrb/builtin/project/add/fastapp/app/template/_automate/snake_zrb_app_name/container/monolith/start.py +7 -2
  6. zrb/builtin/project/add/fastapp/app/template/_automate/snake_zrb_app_name/container/remove.py +8 -2
  7. zrb/builtin/project/add/fastapp/app/template/_automate/snake_zrb_app_name/container/stop.py +8 -2
  8. zrb/builtin/project/add/fastapp/app/template/_automate/snake_zrb_app_name/container/support/start.py +15 -3
  9. zrb/config/config.py +0 -9
  10. zrb/helper/asyncio_task.py +15 -5
  11. zrb/task/base_task/base_task.py +39 -9
  12. zrb/task/cmd_task.py +9 -10
  13. zrb/task/docker_compose_start_task.py +30 -27
  14. zrb/task/docker_compose_task.py +64 -33
  15. zrb/task/looper.py +1 -1
  16. zrb/task/remote_cmd_task.py +55 -18
  17. zrb/task/rsync_task.py +57 -23
  18. {zrb-0.26.2.dist-info → zrb-0.28.0.dist-info}/METADATA +2 -2
  19. {zrb-0.26.2.dist-info → zrb-0.28.0.dist-info}/RECORD +22 -31
  20. zrb/builtin/project/add/fastapp/app/template/_automate/snake_zrb_app_name/container/_helper.py +0 -8
  21. zrb/builtin/project/add/fastapp/app/template/_automate/snake_zrb_app_name/container/microservices/_helper.py +0 -24
  22. zrb/builtin/project/add/fastapp/app/template/_automate/snake_zrb_app_name/container/monolith/_helper.py +0 -22
  23. zrb/builtin/project/add/fastapp/app/template/_automate/snake_zrb_app_name/container/support/_helper.py +0 -30
  24. zrb/builtin/project/add/fastapp/app/template/src/kebab-zrb-app-name/.generator-version +0 -1
  25. zrb/shell-scripts/ensure-podman-is-installed.sh +0 -55
  26. zrb/shell-scripts/rsync-util.sh +0 -12
  27. zrb/shell-scripts/ssh-util.sh +0 -12
  28. zrb/task/base_remote_cmd_task.py +0 -354
  29. {zrb-0.26.2.dist-info → zrb-0.28.0.dist-info}/LICENSE +0 -0
  30. {zrb-0.26.2.dist-info → zrb-0.28.0.dist-info}/WHEEL +0 -0
  31. {zrb-0.26.2.dist-info → zrb-0.28.0.dist-info}/entry_points.txt +0 -0
zrb/__init__.py CHANGED
@@ -10,7 +10,6 @@ from zrb.task.any_task_event_handler import (
10
10
  OnTriggered,
11
11
  OnWaiting,
12
12
  )
13
- from zrb.task.base_remote_cmd_task import BaseRemoteCmdTask, RemoteConfig
14
13
  from zrb.task.checker import Checker
15
14
  from zrb.task.cmd_task import CmdTask
16
15
  from zrb.task.decorator import python_task
@@ -69,8 +68,6 @@ assert CmdTask
69
68
  assert DockerComposeTask
70
69
  assert DockerComposeStartTask
71
70
  assert ServiceConfig
72
- assert BaseRemoteCmdTask
73
- assert RemoteConfig
74
71
  assert RemoteCmdTask
75
72
  assert RsyncTask
76
73
  assert Notifier
zrb/action/runner.py CHANGED
@@ -1,4 +1,3 @@
1
- import sys
2
1
  from collections.abc import Callable, Mapping
3
2
  from typing import Any, Union
4
3
 
@@ -122,7 +121,6 @@ class Runner:
122
121
  function(*args, **kwargs)
123
122
  except Exception:
124
123
  stop_asyncio_sync()
125
- sys.exit(1)
126
124
  finally:
127
125
  task.clear_xcom()
128
126
 
@@ -48,7 +48,7 @@ fi
48
48
  if command_exists chsh
49
49
  then
50
50
  log_info "Changing default shell to zsh..."
51
- try_sudo chsh -s zsh
51
+ chsh -s $(which zsh)
52
52
  else
53
53
  log_info "chsh command not found. Please change the default shell manually."
54
54
  fi
@@ -21,7 +21,6 @@ from .._input import enable_monitoring_input
21
21
  from .._service_config import snake_zrb_app_name_service_configs
22
22
  from ..remove import remove_snake_zrb_app_name_container
23
23
  from ._group import snake_zrb_app_name_microservices_container_group
24
- from ._helper import activate_microservices_compose_profile
25
24
 
26
25
  start_snake_zrb_app_name_microservices_container = DockerComposeStartTask(
27
26
  icon="🐳",
@@ -38,7 +37,13 @@ start_snake_zrb_app_name_microservices_container = DockerComposeStartTask(
38
37
  should_execute="{{ input.local_snake_zrb_app_name}}",
39
38
  upstreams=[build_snake_zrb_app_name_image, remove_snake_zrb_app_name_container],
40
39
  cwd=RESOURCE_DIR,
41
- setup_cmd=activate_microservices_compose_profile,
40
+ compose_profiles=[
41
+ '{{"postgres" if env.APP_DB_CONNECTION.startswith("postgresql") else ""}}',
42
+ '{{"kafka" if env.APP_BROKER_TYPE == "kafka" else ""}}',
43
+ '{{"rabbitmq" if env.APP_BROKER_TYPE == "rabbitmq" else ""}}',
44
+ '{{"monitoring" if input.enable_snake_zrb_app_name_monitoring else ""}}',
45
+ "microservices",
46
+ ],
42
47
  compose_env_prefix="CONTAINER_ZRB_ENV_PREFIX",
43
48
  compose_service_configs=snake_zrb_app_name_service_configs,
44
49
  env_files=[compose_env_file],
@@ -21,7 +21,6 @@ from .._input import enable_monitoring_input
21
21
  from .._service_config import snake_zrb_app_name_service_configs
22
22
  from ..remove import remove_snake_zrb_app_name_container
23
23
  from ._group import snake_zrb_app_name_monolith_container_group
24
- from ._helper import activate_monolith_compose_profile
25
24
 
26
25
  start_snake_zrb_app_name_monolith_container = DockerComposeStartTask(
27
26
  icon="🐳",
@@ -38,7 +37,13 @@ start_snake_zrb_app_name_monolith_container = DockerComposeStartTask(
38
37
  should_execute="{{ input.local_snake_zrb_app_name}}",
39
38
  upstreams=[build_snake_zrb_app_name_image, remove_snake_zrb_app_name_container],
40
39
  cwd=RESOURCE_DIR,
41
- setup_cmd=activate_monolith_compose_profile,
40
+ compose_profiles=[
41
+ '{{"postgres" if env.APP_DB_CONNECTION.startswith("postgresql") else ""}}',
42
+ '{{"kafka" if env.APP_BROKER_TYPE == "kafka" else ""}}',
43
+ '{{"rabbitmq" if env.APP_BROKER_TYPE == "rabbitmq" else ""}}',
44
+ '{{"monitoring" if input.enable_snake_zrb_app_name_monitoring else ""}}',
45
+ "monolith",
46
+ ],
42
47
  compose_env_prefix="CONTAINER_ZRB_ENV_PREFIX",
43
48
  compose_service_configs=snake_zrb_app_name_service_configs,
44
49
  env_files=[compose_env_file],
@@ -5,7 +5,6 @@ from .._constant import RESOURCE_DIR
5
5
  from ..image._env import image_env
6
6
  from ._env import compose_env_file
7
7
  from ._group import snake_zrb_app_name_container_group
8
- from ._helper import activate_all_compose_profile
9
8
  from ._service_config import snake_zrb_app_name_service_configs
10
9
 
11
10
  remove_snake_zrb_app_name_container = DockerComposeTask(
@@ -14,8 +13,15 @@ remove_snake_zrb_app_name_container = DockerComposeTask(
14
13
  description="Remove human readable zrb app name container",
15
14
  group=snake_zrb_app_name_container_group,
16
15
  cwd=RESOURCE_DIR,
17
- setup_cmd=activate_all_compose_profile,
18
16
  compose_cmd="down",
17
+ compose_profiles=[
18
+ "postgres",
19
+ "kafka",
20
+ "rabbitmq",
21
+ "monitoring",
22
+ "monolith",
23
+ "microservices",
24
+ ],
19
25
  compose_env_prefix="CONTAINER_ZRB_ENV_PREFIX",
20
26
  compose_service_configs=snake_zrb_app_name_service_configs,
21
27
  env_files=[compose_env_file],
@@ -5,7 +5,6 @@ from .._constant import RESOURCE_DIR
5
5
  from ..image._env import image_env
6
6
  from ._env import compose_env_file, host_port_env
7
7
  from ._group import snake_zrb_app_name_container_group
8
- from ._helper import activate_all_compose_profile
9
8
  from ._service_config import snake_zrb_app_name_service_configs
10
9
 
11
10
  stop_snake_zrb_app_name_container = DockerComposeTask(
@@ -14,8 +13,15 @@ stop_snake_zrb_app_name_container = DockerComposeTask(
14
13
  description="Stop human readable zrb app name container",
15
14
  group=snake_zrb_app_name_container_group,
16
15
  cwd=RESOURCE_DIR,
17
- setup_cmd=activate_all_compose_profile,
18
16
  compose_cmd="stop",
17
+ compose_profiles=[
18
+ "postgres",
19
+ "kafka",
20
+ "rabbitmq",
21
+ "monitoring",
22
+ "monolith",
23
+ "microservices",
24
+ ],
19
25
  compose_env_prefix="CONTAINER_ZRB_ENV_PREFIX",
20
26
  compose_service_configs=snake_zrb_app_name_service_configs,
21
27
  env_files=[compose_env_file],
@@ -17,7 +17,6 @@ from .._input import enable_monitoring_input
17
17
  from .._service_config import snake_zrb_app_name_service_configs
18
18
  from ..remove import remove_snake_zrb_app_name_container
19
19
  from ._group import snake_zrb_app_name_support_container_group
20
- from ._helper import activate_support_compose_profile, should_start_support_container
21
20
 
22
21
  start_snake_zrb_app_name_support_container = DockerComposeStartTask(
23
22
  icon="🐳",
@@ -31,10 +30,23 @@ start_snake_zrb_app_name_support_container = DockerComposeStartTask(
31
30
  https_input,
32
31
  image_input,
33
32
  ],
34
- should_execute=should_start_support_container,
35
33
  upstreams=[remove_snake_zrb_app_name_container],
36
34
  cwd=RESOURCE_DIR,
37
- setup_cmd=activate_support_compose_profile,
35
+ should_execute=" ".join(
36
+ [
37
+ "{{",
38
+ 'env.APP_DB_CONNECTION.startswith("postgresql")',
39
+ 'or env.APP_BROKER_TYPE in ("kafka", "rabbitmq")',
40
+ "or input.enable_snake_zrb_app_name_monitoring",
41
+ "}}",
42
+ ]
43
+ ),
44
+ compose_profiles=[
45
+ '{{"postgres" if env.APP_DB_CONNECTION.startswith("postgresql") else ""}}',
46
+ '{{"kafka" if env.APP_BROKER_TYPE == "kafka" else ""}}',
47
+ '{{"rabbitmq" if env.APP_BROKER_TYPE == "rabbitmq" else ""}}',
48
+ '{{"monitoring" if input.enable_snake_zrb_app_name_monitoring else ""}}',
49
+ ],
38
50
  compose_env_prefix="CONTAINER_ZRB_ENV_PREFIX",
39
51
  compose_service_configs=snake_zrb_app_name_service_configs,
40
52
  env_files=[compose_env_file],
zrb/config/config.py CHANGED
@@ -18,12 +18,6 @@ def _get_current_shell() -> str:
18
18
  return "bash"
19
19
 
20
20
 
21
- def _get_valid_container_backend(container_backend: str) -> str:
22
- if container_backend.lower().strip() == "podman":
23
- return "podman"
24
- return "docker"
25
-
26
-
27
21
  def _get_default_tmp_dir() -> str:
28
22
  if os.path.isdir("/tmp"):
29
23
  return "/tmp"
@@ -52,7 +46,4 @@ SHOW_ADVERTISEMENT = untyped_to_boolean(os.getenv("ZRB_SHOW_ADVERTISEMENT", "1")
52
46
  SHOW_PROMPT = untyped_to_boolean(os.getenv("ZRB_SHOW_PROMPT", "1"))
53
47
  SHOW_TIME = untyped_to_boolean(os.getenv("ZRB_SHOW_TIME", "1"))
54
48
  VERSION = _get_version()
55
- CONTAINER_BACKEND = _get_valid_container_backend(
56
- os.getenv("ZRB_CONTAINER_BACKEND", "docker")
57
- )
58
49
  ENABLE_TYPE_CHECKING = untyped_to_boolean(os.getenv("ZRB_ENABLE_TYPE_CHECKING", "1"))
@@ -1,4 +1,5 @@
1
1
  import asyncio
2
+ import sys
2
3
 
3
4
  from zrb.helper.accessories.color import colored
4
5
  from zrb.helper.log import logger
@@ -6,11 +7,22 @@ from zrb.helper.log import logger
6
7
  logger.debug(colored("Loading zrb.helper.asyncio_task", attrs=["dark"]))
7
8
 
8
9
 
10
+ def _surpress_event_loop_error(unraisable):
11
+ if not (
12
+ isinstance(unraisable.exc_value, RuntimeError)
13
+ and str(unraisable.exc_value) == "Event loop is closed"
14
+ ):
15
+ # Raise exception for anything except "event loop is closed"
16
+ sys.__unraisablehook__(unraisable)
17
+
18
+
19
+ sys.unraisablehook = _surpress_event_loop_error
20
+
21
+
9
22
  async def stop_asyncio():
10
23
  tasks = [task for task in asyncio.all_tasks() if task is not asyncio.current_task()]
11
24
  for task in tasks:
12
25
  task.cancel()
13
-
14
26
  # Wait until all tasks are cancelled
15
27
  await asyncio.gather(*tasks, return_exceptions=True)
16
28
 
@@ -20,7 +32,5 @@ def stop_asyncio_sync():
20
32
  loop = asyncio.get_event_loop()
21
33
  if loop.is_running():
22
34
  loop.create_task(stop_asyncio())
23
- else:
24
- loop.run_until_complete(stop_asyncio())
25
- except Exception:
26
- pass
35
+ except asyncio.CancelledError:
36
+ logger.warning("Task is cancelled")
@@ -159,16 +159,15 @@ class BaseTask(FinishTracker, AttemptTracker, Renderer, BaseTaskModel, AnyTask):
159
159
  def copy(self) -> AnyTask:
160
160
  return copy.deepcopy(self)
161
161
 
162
- def to_function(
162
+ def __get_async_function(
163
163
  self,
164
164
  env_prefix: str = "",
165
165
  raise_error: bool = True,
166
- is_async: bool = False,
167
166
  show_done_info: bool = True,
168
167
  should_clear_xcom: bool = False,
169
168
  should_stop_looper: bool = False,
170
169
  ) -> Callable[..., Any]:
171
- async def function(*args: Any, **kwargs: Any) -> Any:
170
+ async def async_function(*args: Any, **kwargs: Any) -> Any:
172
171
  self.log_info("Copy task")
173
172
  self_cp: AnyTask = self.copy()
174
173
  result = await self_cp._run_and_check_all(
@@ -184,9 +183,41 @@ class BaseTask(FinishTracker, AttemptTracker, Renderer, BaseTaskModel, AnyTask):
184
183
  self_cp.clear_xcom()
185
184
  return result
186
185
 
186
+ return async_function
187
+
188
+ def __to_sync_function(
189
+ self, async_function: Callable[..., Any]
190
+ ) -> Callable[..., Any]:
191
+ def sync_function(*args: Any, **kwargs: Any) -> Any:
192
+ try:
193
+ return asyncio.run(async_function(*args, **kwargs))
194
+ except RuntimeError as e:
195
+ if "event loop is closed" not in str(e).lower():
196
+ raise e
197
+ except asyncio.CancelledError:
198
+ self.print_out_dark("Task is cancelled")
199
+
200
+ return sync_function
201
+
202
+ def to_function(
203
+ self,
204
+ env_prefix: str = "",
205
+ raise_error: bool = True,
206
+ is_async: bool = False,
207
+ show_done_info: bool = True,
208
+ should_clear_xcom: bool = False,
209
+ should_stop_looper: bool = False,
210
+ ) -> Callable[..., Any]:
211
+ async_function = self.__get_async_function(
212
+ env_prefix=env_prefix,
213
+ raise_error=raise_error,
214
+ show_done_info=show_done_info,
215
+ should_clear_xcom=should_clear_xcom,
216
+ should_stop_looper=should_stop_looper,
217
+ )
187
218
  if is_async:
188
- return function
189
- return lambda *args, **kwargs: asyncio.run(function(*args, **kwargs))
219
+ return async_function
220
+ return self.__to_sync_function(async_function)
190
221
 
191
222
  async def run(self, *args: Any, **kwargs: Any) -> Any:
192
223
  if self._run_function is not None:
@@ -287,9 +318,6 @@ class BaseTask(FinishTracker, AttemptTracker, Renderer, BaseTaskModel, AnyTask):
287
318
  result = results[-1]
288
319
  self._print_result(result)
289
320
  return result
290
- except RuntimeError as e:
291
- if raise_error and ("event loop is closed" not in str(e).lower()):
292
- raise e
293
321
  except Exception as e:
294
322
  self.log_error(f"{e}")
295
323
  if raise_error:
@@ -384,7 +412,9 @@ class BaseTask(FinishTracker, AttemptTracker, Renderer, BaseTaskModel, AnyTask):
384
412
  is_failed: bool = False
385
413
  while self._should_attempt():
386
414
  try:
387
- self.log_debug(f"Started with args: {args} and kwargs: {local_kwargs}")
415
+ self.log_debug(
416
+ f"Started with args: {args} and kwargs: {local_kwargs}"
417
+ ) # noqa
388
418
  await self.on_started()
389
419
  self.__running_tasks.append(self)
390
420
  result = await run_async(self.run, *args, **local_kwargs)
zrb/task/cmd_task.py CHANGED
@@ -291,8 +291,6 @@ class CmdTask(BaseTask):
291
291
  for pid in self._pids:
292
292
  self.__kill_by_pid(pid)
293
293
  stop_asyncio_sync()
294
- _print_out_dark(f"Exiting with signal {signum}")
295
- sys.exit(signum)
296
294
 
297
295
  def __on_exit(self):
298
296
  self._global_state.no_more_attempt = True
@@ -351,14 +349,15 @@ class CmdTask(BaseTask):
351
349
  )
352
350
  )
353
351
  # wait process
354
- await process.wait()
355
- # wait reader and logger
356
- await stdout_process
357
- await stderr_process
358
- await stdout_queue.put(None)
359
- await stderr_queue.put(None)
360
- await stdout_log_process
361
- await stderr_log_process
352
+ await asyncio.gather(
353
+ process.wait(),
354
+ stdout_process,
355
+ stderr_process,
356
+ )
357
+ # stop messages in queue
358
+ await asyncio.gather(stdout_queue.put(None), stderr_queue.put(None))
359
+ # end logging
360
+ await asyncio.gather(stdout_log_process, stderr_log_process)
362
361
 
363
362
  def get_cmd_script(self, *args: Any, **kwargs: Any) -> str:
364
363
  return self._create_cmd_script(self._cmd_path, self._cmd, *args, **kwargs)
@@ -45,6 +45,7 @@ class DockerComposeStartTask(DockerComposeTask):
45
45
  compose_options: Mapping[JinjaTemplate, JinjaTemplate] = {},
46
46
  compose_flags: Iterable[JinjaTemplate] = [],
47
47
  compose_args: Iterable[JinjaTemplate] = [],
48
+ compose_profiles: Iterable[JinjaTemplate] = [],
48
49
  compose_env_prefix: str = "",
49
50
  setup_cmd: CmdVal = "",
50
51
  setup_cmd_path: CmdVal = "",
@@ -85,9 +86,11 @@ class DockerComposeStartTask(DockerComposeTask):
85
86
  executable=executable,
86
87
  compose_service_configs=compose_service_configs,
87
88
  compose_file=compose_file,
89
+ compose_cmd="up",
88
90
  compose_options=compose_options,
89
91
  compose_flags=compose_flags,
90
92
  compose_args=compose_args,
93
+ compose_profiles=compose_profiles,
91
94
  compose_env_prefix=compose_env_prefix,
92
95
  setup_cmd=setup_cmd,
93
96
  setup_cmd_path=setup_cmd_path,
@@ -116,31 +119,31 @@ class DockerComposeStartTask(DockerComposeTask):
116
119
  should_show_working_directory=should_show_working_directory,
117
120
  )
118
121
 
119
- def get_cmd_script(self, *args: Any, **kwargs: Any) -> str:
120
- # setup
121
- setup_cmd = self._create_cmd_script(
122
- self._setup_cmd_path, self._setup_cmd, *args, **kwargs
122
+ def _get_execute_docker_compose_script(
123
+ self,
124
+ compose_cmd: JinjaTemplate,
125
+ compose_options: Mapping[JinjaTemplate, JinjaTemplate],
126
+ compose_flags: Iterable[JinjaTemplate],
127
+ compose_args: Iterable[JinjaTemplate],
128
+ *args: Any
129
+ ) -> JinjaTemplate:
130
+ return "\n".join(
131
+ [
132
+ # compose start
133
+ super()._get_execute_docker_compose_script(
134
+ compose_cmd=compose_cmd,
135
+ compose_options=compose_options,
136
+ compose_flags=list(compose_flags) + ["-d"],
137
+ compose_args=compose_args,
138
+ *args,
139
+ ),
140
+ # compose log
141
+ super()._get_execute_docker_compose_script(
142
+ compose_cmd="logs",
143
+ compose_options={},
144
+ compose_flags=["-f"],
145
+ compose_args=[],
146
+ *args,
147
+ ),
148
+ ]
123
149
  )
124
- cmd_list = [setup_cmd] if setup_cmd.strip() != "" else []
125
- # compose command
126
- cmd_list = cmd_list + [
127
- # compose start
128
- self._get_docker_compose_cmd_script(
129
- compose_cmd="up",
130
- compose_options=self._compose_options,
131
- compose_flags=list(self._compose_flags) + ["-d"],
132
- compose_args=self._compose_args,
133
- *args,
134
- ),
135
- # compose log
136
- self._get_docker_compose_cmd_script(
137
- compose_cmd="logs",
138
- compose_options={},
139
- compose_flags=["-f"],
140
- compose_args=[],
141
- *args,
142
- ),
143
- ]
144
- cmd_str = "\n".join(cmd_list)
145
- self.log_info(f"Command: {cmd_str}")
146
- return cmd_str
@@ -3,7 +3,6 @@ import pathlib
3
3
  from collections.abc import Callable, Iterable, Mapping
4
4
  from typing import Any, Optional, TypeVar, Union
5
5
 
6
- from zrb.config.config import CONTAINER_BACKEND
7
6
  from zrb.helper.accessories.color import colored
8
7
  from zrb.helper.accessories.name import get_random_name
9
8
  from zrb.helper.docker_compose.fetch_external_env import fetch_compose_file_env_map
@@ -33,32 +32,22 @@ from zrb.task_input.any_input import AnyInput
33
32
 
34
33
  logger.debug(colored("Loading zrb.task.docker_compose_task", attrs=["dark"]))
35
34
 
35
+ TDockerComposeTask = TypeVar("TDockerComposeTask", bound="DockerComposeTask")
36
36
 
37
- def _get_ensure_zrb_network_task(backend: str):
38
- CURRENT_DIR = os.path.dirname(__file__)
39
- SHELL_SCRIPT_DIR = os.path.join(CURRENT_DIR, "..", "shell-scripts")
40
- ensure_container_backend = CmdTask(
41
- name="ensure-compose-backend",
42
- cmd_path=[
43
- os.path.join(SHELL_SCRIPT_DIR, "_common-util.sh"),
44
- os.path.join(SHELL_SCRIPT_DIR, f"ensure-{backend}-is-installed.sh"),
45
- ],
46
- preexec_fn=None,
47
- should_print_cmd_result=False,
48
- )
49
- return CmdTask(
50
- name="ensure-zrb-network",
51
- cmd=[
52
- f"{backend} network inspect zrb >/dev/null 2>&1 || \\",
53
- f"{backend} network create -d bridge zrb",
54
- ],
55
- upstreams=[ensure_container_backend],
56
- should_print_cmd_result=False,
57
- )
58
-
37
+ CURRENT_DIR = os.path.dirname(__file__)
38
+ SHELL_SCRIPT_DIR = os.path.join(CURRENT_DIR, "..", "shell-scripts")
59
39
 
60
- TDockerComposeTask = TypeVar("TDockerComposeTask", bound="DockerComposeTask")
61
- ensure_zrb_network_task = _get_ensure_zrb_network_task(CONTAINER_BACKEND)
40
+ ensure_container_backend = CmdTask(
41
+ name="ensure-compose-backend",
42
+ cmd_path=[
43
+ os.path.join(SHELL_SCRIPT_DIR, "_common-util.sh"),
44
+ os.path.join(SHELL_SCRIPT_DIR, "ensure-docker-is-installed.sh"),
45
+ ],
46
+ preexec_fn=None,
47
+ should_print_cmd_result=False,
48
+ should_show_cmd=False,
49
+ should_show_working_directory=False,
50
+ )
62
51
 
63
52
 
64
53
  @typechecked
@@ -93,6 +82,7 @@ class DockerComposeTask(CmdTask):
93
82
  compose_options: Mapping[JinjaTemplate, JinjaTemplate] = {},
94
83
  compose_flags: Iterable[JinjaTemplate] = [],
95
84
  compose_args: Iterable[JinjaTemplate] = [],
85
+ compose_profiles: CmdVal = "",
96
86
  compose_env_prefix: str = "",
97
87
  setup_cmd: CmdVal = "",
98
88
  setup_cmd_path: CmdVal = "",
@@ -120,21 +110,20 @@ class DockerComposeTask(CmdTask):
120
110
  should_show_cmd: bool = True,
121
111
  should_show_working_directory: bool = True,
122
112
  ):
123
- combined_env_files = list(env_files)
124
113
  CmdTask.__init__(
125
114
  self,
126
115
  name=name,
127
116
  group=group,
128
117
  inputs=inputs,
129
118
  envs=envs,
130
- env_files=combined_env_files,
119
+ env_files=env_files,
131
120
  icon=icon,
132
121
  color=color,
133
122
  description=description,
134
123
  executable=executable,
135
124
  cwd=cwd,
136
125
  should_render_cwd=should_render_cwd,
137
- upstreams=[ensure_zrb_network_task] + upstreams,
126
+ upstreams=[ensure_container_backend] + upstreams,
138
127
  fallbacks=fallbacks,
139
128
  on_triggered=on_triggered,
140
129
  on_waiting=on_waiting,
@@ -168,6 +157,7 @@ class DockerComposeTask(CmdTask):
168
157
  self._compose_flags = compose_flags
169
158
  self._compose_args = compose_args
170
159
  self._compose_env_prefix = compose_env_prefix
160
+ self._compose_profiles = compose_profiles
171
161
  self._compose_template_file = self.__get_compose_template_file(compose_file)
172
162
  self._compose_runtime_file = self.__get_compose_runtime_file(
173
163
  self._compose_template_file
@@ -333,14 +323,24 @@ class DockerComposeTask(CmdTask):
333
323
  raise Exception(f"Invalid compose file: {compose_file}")
334
324
 
335
325
  def get_cmd_script(self, *args: Any, **kwargs: Any) -> str:
326
+ cmd_list = []
327
+ # create network
328
+ create_network_script = self._get_create_compose_network_script()
329
+ if create_network_script.strip() != "":
330
+ cmd_list.append(create_network_script)
331
+ # set compose profiles
332
+ compose_profile_script = self._get_compose_profile_script(*args, **kwargs)
333
+ if compose_profile_script.strip() != "":
334
+ cmd_list.append(compose_profile_script)
336
335
  # setup
337
- setup_cmd = self._create_cmd_script(
336
+ setup_script = self._create_cmd_script(
338
337
  self._setup_cmd_path, self._setup_cmd, *args, **kwargs
339
338
  )
340
- cmd_list = [setup_cmd] if setup_cmd.strip() != "" else []
339
+ if setup_script.strip() != "":
340
+ cmd_list.append(setup_script)
341
341
  # compose command
342
342
  cmd_list.append(
343
- self._get_docker_compose_cmd_script(
343
+ self._get_execute_docker_compose_script(
344
344
  compose_cmd=self._compose_cmd,
345
345
  compose_options=self._compose_options,
346
346
  compose_flags=self._compose_flags,
@@ -352,7 +352,38 @@ class DockerComposeTask(CmdTask):
352
352
  self.log_info(f"Command: {cmd_str}")
353
353
  return cmd_str
354
354
 
355
- def _get_docker_compose_cmd_script(
355
+ def _get_compose_profile_script(self, *args, **kwargs) -> str:
356
+ # Get list representation of self._compose_profiles
357
+ compose_profiles = self._compose_profiles
358
+ if callable(compose_profiles):
359
+ compose_profiles = self._compose_profiles(*args, **kwargs)
360
+ if isinstance(compose_profiles, str):
361
+ compose_profiles = compose_profiles.split(",")
362
+ # Get only non empty profiles
363
+ filtered_compose_profiles = [
364
+ self.render_str(profile)
365
+ for profile in compose_profiles
366
+ if self.render_str(profile).strip() != ""
367
+ ]
368
+ if len(filtered_compose_profiles) == 0:
369
+ return ""
370
+ compose_profiles_str = ",".join(filtered_compose_profiles)
371
+ return f"export COMPOSE_PROFILES={compose_profiles_str}"
372
+
373
+ def _get_create_compose_network_script(self) -> str:
374
+ compose_data = read_compose_file(self._compose_runtime_file)
375
+ networks: Mapping[str, Mapping[str, Any]] = compose_data.get("networks", {})
376
+ scripts = []
377
+ for key, config in networks.items():
378
+ if not config.get("external", False):
379
+ continue
380
+ network_name = config.get("name", key)
381
+ scripts.append(
382
+ f"docker network inspect {network_name} > /dev/null 2>&1 || docker network create -d bridge{network_name}" # noqa
383
+ )
384
+ return "\n".join(scripts)
385
+
386
+ def _get_execute_docker_compose_script(
356
387
  self,
357
388
  compose_cmd: str,
358
389
  compose_options: Mapping[JinjaTemplate, JinjaTemplate],
@@ -383,4 +414,4 @@ class DockerComposeTask(CmdTask):
383
414
  if self.render_str(arg) != ""
384
415
  ]
385
416
  )
386
- return f"{CONTAINER_BACKEND} compose {options} {compose_cmd} {flags} {args}"
417
+ return f"docker compose {options} {compose_cmd} {flags} {args}"
zrb/task/looper.py CHANGED
@@ -1,4 +1,4 @@
1
- from collections.abc import Callable
1
+ from collections.abc import Callable, Mapping
2
2
  from typing import Optional
3
3
 
4
4
  from zrb.helper.accessories.color import colored