service.py 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  1. # Copyright Materialize, Inc. and contributors. All rights reserved.
  2. #
  3. # Use of this software is governed by the Business Source License
  4. # included in the LICENSE file at the root of this repository.
  5. #
  6. # As of the Change Date specified in that file, in accordance with
  7. # the Business Source License, use of this software will be governed
  8. # by the Apache License, Version 2.0.
  9. """The implementation of the mzcompose system for Docker compositions.
  10. For an overview of what mzcompose is and why it exists, see the [user-facing
  11. documentation][user-docs].
  12. [user-docs]: https://github.com/MaterializeInc/materialize/blob/main/doc/developer/mzbuild.md
  13. """
  14. from collections.abc import Sequence
  15. from typing import (
  16. Any,
  17. TypedDict,
  18. )
  19. class ServiceHealthcheck(TypedDict, total=False):
  20. """Configuration for a check to determine whether the containers for this
  21. service are healthy."""
  22. test: list[str] | str
  23. """A specification of a command to run."""
  24. interval: str
  25. """The interval at which to run the healthcheck."""
  26. timeout: str
  27. """The maximum amount of time that the test command can run before it
  28. is considered failed."""
  29. retries: int
  30. """The number of consecutive healthchecks that must fail for the container
  31. to be considered unhealthy."""
  32. start_period: str
  33. """The period after container start during which failing healthchecks will
  34. not be counted towards the retry limit."""
  35. class ServiceDependency(TypedDict, total=False):
  36. """Configuration for a check to determine whether the containers for this
  37. service are healthy."""
  38. condition: str
  39. """Condition under which a dependency is considered satisfied."""
  40. class ServiceConfig(TypedDict, total=False):
  41. """The definition of a service in Docker Compose.
  42. This object corresponds directly to the YAML definition in a
  43. docker-compose.yml file, plus two mzcompose-specific attributes. Full
  44. details are available in [Services top-level element][ref] chapter of the
  45. Compose Specification.
  46. [ref]: https://github.com/compose-spec/compose-spec/blob/master/spec.md#services-top-level-element
  47. """
  48. mzbuild: str
  49. """The name of an mzbuild image to dynamically acquire before invoking
  50. Docker Compose.
  51. This is a mzcompose-extension to Docker Compose. The image must exist in
  52. the repository. If `mzbuild` is set, neither `build` nor `image` should be
  53. set.
  54. """
  55. propagate_uid_gid: bool
  56. """Request that the Docker image be run with the user ID and group ID of the
  57. host user.
  58. This is an mzcompose extension to Docker Compose. It is equivalent to
  59. passing `--user $(id -u):$(id -g)` to `docker run`. The default is `False`.
  60. """
  61. allow_host_ports: bool
  62. """Allow the service to map host ports in its `ports` configuration.
  63. This option is intended only for compositions that are meant to be run as
  64. background services in developer environments. Compositions that are
  65. isolated tests of Materialize should *not* enable this option, as it leads
  66. to unnecessary conflicts between compositions. Compositions that publish the
  67. same host port cannot be run concurrently. Instead, users should use the
  68. `mzcompose port` command to discover the ephemeral host port mapped to the
  69. desired container port, or to use `mzcompose up --preserve-ports`, which
  70. publishes all container ports as host ports on a per-invocation basis.
  71. """
  72. image: str
  73. """The name and tag of an image on Docker Hub."""
  74. hostname: str
  75. """The hostname to use.
  76. By default, the container's ID is used as the hostname.
  77. """
  78. extra_hosts: list[str]
  79. """Additional hostname mappings."""
  80. entrypoint: list[str]
  81. """Override the entrypoint specified in the image."""
  82. command: list[str]
  83. """Override the command specified in the image."""
  84. init: bool
  85. """Whether to run an init process in the container."""
  86. ports: Sequence[int | str]
  87. """Service ports to expose to the host."""
  88. environment: list[str]
  89. """Additional environment variables to set.
  90. Each entry must be in the form `NAME=VALUE`.
  91. TODO(benesch): this should accept a `dict[str, str]` instead.
  92. """
  93. depends_on: list[str] | dict[str, ServiceDependency]
  94. """The list of other services that must be started before this one."""
  95. tmpfs: list[str]
  96. """Paths at which to mount temporary file systems inside the container."""
  97. volumes: list[str]
  98. """Volumes to attach to the service."""
  99. networks: dict[str, dict[str, list[str]]]
  100. """Additional networks to join.
  101. TODO(benesch): this should use a nested TypedDict.
  102. """
  103. deploy: dict[str, dict[str, dict[str, str]]]
  104. """Additional deployment configuration, like resource limits.
  105. TODO(benesch): this should use a nested TypedDict.
  106. """
  107. ulimits: dict[str, Any]
  108. """Override the default ulimits for a container."""
  109. working_dir: str
  110. """Overrides the container's working directory."""
  111. healthcheck: ServiceHealthcheck
  112. """Configuration for a check to determine whether the containers for this
  113. service are healthy."""
  114. restart: str
  115. """Restart policy."""
  116. labels: dict[str, Any]
  117. """Container labels."""
  118. platform: str
  119. """Target platform for service to run on. Syntax: os[/arch[/variant]]"""
  120. publish: bool | None
  121. """Override whether an image is publishable. Unpublishable images can be built during normal test runs in CI."""
  122. stop_grace_period: str | None
  123. """Time to wait when stopping a container."""
  124. network_mode: str | None
  125. """Network mode."""
  126. class Service:
  127. """A Docker Compose service in a `Composition`.
  128. Attributes:
  129. name: The name of the service.
  130. config: The definition of the service.
  131. """
  132. def __init__(self, name: str, config: ServiceConfig) -> None:
  133. self.name = name
  134. self.config = config