diff --git a/.gitignore b/.gitignore index c10ddcb9..c6361be6 100644 --- a/.gitignore +++ b/.gitignore @@ -129,3 +129,26 @@ dmypy.json .pyre/ docs/generated_sources docs/site + +### Intellij+all ### +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml diff --git a/python_on_whales/components/compose/cli_wrapper.py b/python_on_whales/components/compose/cli_wrapper.py index 84da9545..117b4a4b 100644 --- a/python_on_whales/components/compose/cli_wrapper.py +++ b/python_on_whales/components/compose/cli_wrapper.py @@ -132,7 +132,8 @@ def config(self, return_json: bool = False) -> Union[ComposeConfig, Dict[str, An if return_json: return json.loads(result) else: - return ComposeConfig(**json.loads(result)) + raw_compose_config = json.loads(result) + return ComposeConfig(**raw_compose_config) @overload def create( diff --git a/python_on_whales/components/compose/models.py b/python_on_whales/components/compose/models.py index d81ed707..5bc4f9d2 100644 --- a/python_on_whales/components/compose/models.py +++ b/python_on_whales/components/compose/models.py @@ -39,7 +39,20 @@ class ComposeServiceBuild(BaseModel): context: Optional[Path] = None dockerfile: Optional[Path] = None args: Optional[Dict[str, Any]] = None + cache_from: Optional[List[str]] = None labels: Optional[Dict[str, Any]] = None + network: Optional[str] = None + target: Optional[str] = None + + +class ComposeServiceHealthcheck(BaseModel): + disable: Optional[bool] = None + interval: Optional[str] = None + start_period: Optional[str] = None + start_interval: Optional[str] = None + test: Optional[List[str]] = None + timeout: Optional[str] = None + retries: Optional[int] = None class ComposeServicePort(BaseModel): @@ -49,6 +62,16 @@ class ComposeServicePort(BaseModel): target: Optional[int] = None +class ComposeServiceULimitsNoFile(BaseModel): + soft: Optional[int] = None + hard: Optional[int] = None + + +class ComposeServiceULimits(BaseModel): + nproc: Optional[int] = None + nofile: Optional[ComposeServiceULimitsNoFile] = None + + class ComposeServiceVolume(BaseModel): bind: Optional[dict] = None source: Optional[str] = None @@ -57,27 +80,45 @@ class ComposeServiceVolume(BaseModel): class ComposeConfigService(BaseModel): - deploy: Optional[ServiceDeployConfig] = None blkio_config: Optional[Any] = None - cpu_count: Optional[float] = None - cpu_percent: Optional[float] = None - cpu_shares: Optional[int] = None - cpuset: Optional[str] = None build: Optional[ComposeServiceBuild] = None cap_add: Annotated[Optional[List[str]], Field(default_factory=list)] cap_drop: Annotated[Optional[List[str]], Field(default_factory=list)] - cgroup_parent: Optional[str] = None command: Optional[List[str]] = None - configs: Any = None + cgroup_parent: Optional[str] = None container_name: Optional[str] = None + cpu_count: Optional[float] = None + cpu_percent: Optional[float] = None + cpu_shares: Optional[int] = None + cpuset: Optional[str] = None depends_on: Annotated[Dict[str, DependencyCondition], Field(default_factory=dict)] + deploy: Optional[ServiceDeployConfig] = None + devices: List[str] = None device_cgroup_rules: Annotated[List[str], Field(default_factory=list)] - devices: Any = None - environment: Optional[Dict[str, Optional[str]]] = None + dns: Optional[List[str]] = None + dns_search: Optional[List[str]] = None entrypoint: Optional[List[str]] = None + environment: Optional[Dict[str, Optional[str]]] = None + expose: Optional[List[str]] = None + external_links: Optional[List[str]] = None + extra_hosts: Optional[List[str]] = None + healthcheck: Optional[ComposeServiceHealthcheck] = None image: Optional[str] = None + init: Optional[bool] = False + isolation: str = Field(default="default") labels: Annotated[Optional[Dict[str, str]], Field(default_factory=dict)] + network_mode: Optional[str] = None + networks: Optional[Any] = None + pid: Optional[str] = None ports: Optional[List[ComposeServicePort]] = None + profiles: Optional[List[str]] = None + restart: str = "no" + secrets: Optional[List[Dict[str, Any]]] = None + stop_grace_period: str = Field(default="10s") + stop_signal: str = Field(default="SIGTERM") + tmpfs: Optional[List[str]] = None + ulimits: Optional[ComposeServiceULimits] = None + userns_mode: Optional[str] = None volumes: Optional[List[ComposeServiceVolume]] = None diff --git a/tests/python_on_whales/components/complex-compose.yml b/tests/python_on_whales/components/complex-compose.yml new file mode 100644 index 00000000..bdb6567c --- /dev/null +++ b/tests/python_on_whales/components/complex-compose.yml @@ -0,0 +1,66 @@ +version: "3.7" + +services: + my_service: + build: + context: my_service_build + image: some_random_image + command: ping -c 2 www.google.com + ports: + - "5000:5000" + volumes: + - /tmp:/tmp + - dodo:/dodo + environment: + - DATADOG_HOST=something + dns: 8.8.8.8 + dns_search: + - dc1.example.com + - dc2.example.com + expose: + - "5000" + - 8000 + external_links: + - "redis" + extra_hosts: + - "somehost:162.242.195.82" + - "otherhost:50.31.209.229" + healthcheck: + test: [ "CMD", "curl", "-f", "http://localhost" ] + interval: 1m30s + timeout: 10s + retries: 3 + start_period: 40s + init: true + isolation: "process" + network_mode: "host" + pid: "host" + secrets: + - my_secret + tmpfs: /run + ulimits: + nproc: 65535 + nofile: + soft: 20000 + hard: 40000 + userns_mode: "host" + deploy: + placement: + constraints: + - node.labels.hello-world == yes + resources: + reservations: + cpus: '1' + memory: 20M + limits: + cpus: '2' + memory: 40M + replicas: 4 + +secrets: + my_secret: + external: true + + +volumes: + dodo: {} diff --git a/tests/python_on_whales/components/complexe-compose.yml b/tests/python_on_whales/components/complexe-compose.yml deleted file mode 100644 index 24a0f62b..00000000 --- a/tests/python_on_whales/components/complexe-compose.yml +++ /dev/null @@ -1,32 +0,0 @@ -version: "3.7" - -services: - my_service: - build: - context: my_service_build - image: some_random_image - command: ping -c 2 www.google.com - ports: - - "5000:5000" - volumes: - - /tmp:/tmp - - dodo:/dodo - environment: - - DATADOG_HOST=something - deploy: - placement: - constraints: - - node.labels.hello-world == yes - resources: - reservations: - cpus: '1' - memory: 20M - limits: - cpus: '2' - memory: 40M - replicas: 4 - - - -volumes: - dodo: {} diff --git a/tests/python_on_whales/components/test-build-args.yml b/tests/python_on_whales/components/test-build-args.yml index fe23517b..c33f3640 100644 --- a/tests/python_on_whales/components/test-build-args.yml +++ b/tests/python_on_whales/components/test-build-args.yml @@ -1,4 +1,4 @@ -version: "3.7" +version: "3.9" services: my_service: @@ -8,9 +8,14 @@ services: args: python_version: "3.78" python_version_1: "3.78" + cache_from: + - alpine:latest + - corp/web_app:3.14 labels: com.example.description: "Accounting webapp" com.example.department: "Finance" + network: "host" + target: "prod" image: "some_random_image" command: ping -c 7 www.google.com ports: diff --git a/tests/python_on_whales/components/test_compose.py b/tests/python_on_whales/components/test_compose.py index e52e0fff..8311f416 100644 --- a/tests/python_on_whales/components/test_compose.py +++ b/tests/python_on_whales/components/test_compose.py @@ -514,10 +514,10 @@ def test_entrypoint_loaded_in_config(): assert docker.compose.config().services["dodo"].entrypoint == ["/bin/sh"] -def test_config_complexe_compose(): +def test_config_complex_compose(): """Checking that the pydantic model does its job""" compose_file = ( - PROJECT_ROOT / "tests/python_on_whales/components/complexe-compose.yml" + PROJECT_ROOT / "tests/python_on_whales/components/complex-compose.yml" ) docker = DockerClient(compose_files=[compose_file], compose_compatibility=True) config = docker.compose.config() @@ -540,6 +540,49 @@ def test_config_complexe_compose(): assert config.services["my_service"].volumes[1].target == "/dodo" assert config.services["my_service"].environment == {"DATADOG_HOST": "something"} + + assert config.services["my_service"].dns == ["8.8.8.8"] + assert config.services["my_service"].dns_search == [ + "dc1.example.com", + "dc2.example.com", + ] + + assert config.services["my_service"].expose == ["5000", "8000"] + + assert config.services["my_service"].external_links == ["redis"] + + assert config.services["my_service"].extra_hosts == [ + "otherhost:50.31.209.229", + "somehost:162.242.195.82", + ] + + assert config.services["my_service"].healthcheck.test == [ + "CMD", + "curl", + "-f", + "http://localhost", + ] + assert config.services["my_service"].healthcheck.interval == "1m30s" + assert config.services["my_service"].healthcheck.timeout == "10s" + assert config.services["my_service"].healthcheck.retries == 3 + assert config.services["my_service"].healthcheck.start_period == "40s" + + assert config.services["my_service"].init + assert config.services["my_service"].isolation == "process" + assert config.services["my_service"].network_mode == "host" + assert config.services["my_service"].pid == "host" + assert config.services["my_service"].restart == "no" + assert config.services["my_service"].secrets == [{"source": "my_secret"}] + assert config.services["my_service"].stop_grace_period == "10s" + assert config.services["my_service"].stop_signal == "SIGTERM" + assert config.services["my_service"].tmpfs == ["/run"] + + assert config.services["my_service"].ulimits.nproc == 65535 + assert config.services["my_service"].ulimits.nofile.soft == 20000 + assert config.services["my_service"].ulimits.nofile.hard == 40000 + + assert config.services["my_service"].userns_mode == "host" + assert config.services["my_service"].deploy.placement.constraints == [ "node.labels.hello-world == yes" ] @@ -1009,10 +1052,17 @@ def test_build_args(): "python_version": "3.78", "python_version_1": "3.78", } + assert config.services["my_service"].build.cache_from == [ + "alpine:latest", + "corp/web_app:3.14", + ] assert config.services["my_service"].build.labels == { "com.example.description": "Accounting webapp", "com.example.department": "Finance", } + assert config.services["my_service"].build.network == "host" + assert config.services["my_service"].build.target == "prod" + assert config.services["my_service"].image == "some_random_image" assert config.services["my_service"].command == [ "ping",