diff --git a/ecs_composex/compose/compose_services/__init__.py b/ecs_composex/compose/compose_services/__init__.py index f96d8c838..acbbb6fcb 100644 --- a/ecs_composex/compose/compose_services/__init__.py +++ b/ecs_composex/compose/compose_services/__init__.py @@ -889,11 +889,17 @@ def define_port_mappings(self) -> list: for target_port, published_ports in mappings.items(): if published_ports: for port in published_ports: + published_port, service_port = port service_port_mappings.append( PortMapping( ContainerPort=target_port, - HostPort=If(USE_FARGATE_CON_T, NoValue, port), + HostPort=If(USE_FARGATE_CON_T, NoValue, published_port), Protocol=protocol.lower(), + Name=set_else_none( + "name", + service_port, + f"{protocol.lower()}_{target_port}", + ), ) ) else: @@ -902,6 +908,7 @@ def define_port_mappings(self) -> list: ContainerPort=target_port, HostPort=NoValue, Protocol=protocol.lower(), + Name=f"{protocol.lower()}_{target_port}", ) ) self.handle_expose_ports(service_port_mappings) diff --git a/ecs_composex/compose/compose_services/helpers.py b/ecs_composex/compose/compose_services/helpers.py index 001757116..30ede3ca7 100644 --- a/ecs_composex/compose/compose_services/helpers.py +++ b/ecs_composex/compose/compose_services/helpers.py @@ -3,22 +3,28 @@ from __future__ import annotations -from compose_x_common.compose_x_common import keyisset +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from troposphere import Template + from troposphere.ecs import ContainerDefinition, Secret + from ecs_composex.common.settings import ComposeXSettings + from ecs_composex.compose.compose_services import ComposeService + +from compose_x_common.compose_x_common import keyisset, set_else_none from troposphere import FindInMap, GetAtt, ImportValue, NoValue, Ref, Sub from troposphere.ecs import ContainerDefinition, Environment from ecs_composex.common.logging import LOG -def import_secrets(template, service, container, settings): - """ - Function to import secrets from compose-x mapping to AWS Secrets in Secrets Manager - - :param troposphere.Template template: - :param troposhere.ecs.ContainerDefinition container: - :param ecs_composex.common.settings.ComposeXSettings settings: - :return: - """ +def import_secrets( + template: Template, + service: ComposeService, + container: ContainerDefinition, + settings: ComposeXSettings, +): + """Function to import secrets from compose-x and map those to AWS Secrets in Secrets Manager""" if not service.secrets: return if not keyisset("secrets", settings.compose_content): @@ -42,7 +48,7 @@ def import_secrets(template, service, container, settings): ].assign_to_task_definition(template, container) -def define_string_interpolation(var_value): +def define_string_interpolation(var_value: str) -> str: """ Function to determine whether an env variable string should use Sub. @@ -56,14 +62,8 @@ def define_string_interpolation(var_value): return var_value -def set_environment_dict_from_list(environment: list) -> dict: - """ - Transforms a list of string with a ``key=value`` into a dict of key/value - - :param list environment: - :rtype: dict - :return: dict of key/value - """ +def set_environment_dict_from_list(environment: list) -> dict[str, str]: + """Transforms a list of string with a ``key=value`` into a dict of key/value""" env_vars_to_map = {} for key in environment: if not isinstance(key, str) or key.find(r"=") < 0: @@ -111,15 +111,8 @@ def import_env_variables(environment) -> list: return env_vars -def extend_container_secrets(container, secret): - """ - Function to add secrets to a Container definition - - :param container: container definition - :type container: troposphere.ecs.ContainerDefinition - :param secret: secret to add - :type secret: troposphere.ecs.Secret - """ +def extend_container_secrets(container: ContainerDefinition, secret: Secret): + """Add secrets to a Container definition""" if hasattr(container, "Secrets"): secrets = getattr(container, "Secrets") if secrets: @@ -150,13 +143,7 @@ def set_validate_environment(container: ContainerDefinition) -> None: def extend_container_envvars( container: ContainerDefinition, env_vars: list, replace: bool = False ) -> None: - """ - Extends the container environment variables with new ones to add. If not already set, defines. - - :param troposphere.ecs.ContainerDefinition container: - :param list[troposphere.ecs.Environment] env_vars: - :return: - """ + """Extends the container environment variables with new ones to add. If not already set, defines.""" ignored_containers = ["xray-daemon", "envoy", "cw_agent"] if ( isinstance(container, ContainerDefinition) @@ -196,7 +183,7 @@ def extend_container_envvars( ) -def define_ingress_mappings(service_ports): +def define_ingress_mappings(service_ports: list) -> dict: """ Function to create a mapping of sources for a common target """ @@ -206,21 +193,22 @@ def define_ingress_mappings(service_ports): for port in service_ports: if not keyisset("target", port): raise KeyError("The ports must always at least define the target.") - if keyisset("protocol", port) and port["protocol"] == "udp": + _port_protocol = set_else_none("protocol", port, "tcp") + _port_target = port["target"] + _port_published = set_else_none("published", port, None) + + if _port_protocol == "udp": mappings = udp_mappings else: mappings = tcp_mappings - if not port["target"] in mappings.keys() and keyisset("published", port): - mappings[port["target"]] = [port["published"]] - - elif not port["target"] in mappings.keys() and not keyisset("published", port): - mappings[port["target"]] = [] - elif ( - port["target"] in mappings.keys() - and not port["published"] in mappings[port["target"]] - ): - mappings[port["target"]].append(port["published"]) + if _port_target not in mappings: + if _port_published: + mappings[_port_target] = [(_port_published, service_ports)] + else: + mappings[_port_target] = [] + elif _port_target in mappings and _port_published not in mappings[_port_target]: + mappings[_port_target].append((_port_published, service_ports)) return ports_mappings diff --git a/ecs_composex/compose/x_resources/services_resources.py b/ecs_composex/compose/x_resources/services_resources.py index 498f0d3ab..34baeab6c 100644 --- a/ecs_composex/compose/x_resources/services_resources.py +++ b/ecs_composex/compose/x_resources/services_resources.py @@ -211,8 +211,9 @@ def set_services_scaling(self, settings: ComposeXSettings): LOG.debug(f"{self.module.res_key}.{self.name} No Services defined.") return if not isinstance(self.services, dict): - raise TypeError( + TypeError( "Services scaling must be in a mapping/dict format." "List format has been deprecated since 1.0" ) + return self.set_services_targets_scaling_from_dict(settings) diff --git a/ecs_composex/specs/compose-spec.json b/ecs_composex/specs/compose-spec.json index 5ba50ceb2..bbcc1f9d4 100644 --- a/ecs_composex/specs/compose-spec.json +++ b/ecs_composex/specs/compose-spec.json @@ -1,21 +1,31 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "compose-spec.json", + "$schema": "https://json-schema.org/draft/2019-09/schema#", + "id": "compose_spec.json", "type": "object", "title": "Compose Specification", - "description": "Compose specification from compose-spec with added specifications for ECS Compose-X", - "$comment": "Compose specification from compose-spec with added specifications for ECS Compose-X. Source gh:from compose-spec/compose-spec/schema", + "description": "The Compose file is a YAML file defining a multi-containers based application.", + "properties": { - "x-tags": { - "$ref": "x-tags.spec.json" - }, - "x-cluster": { - "$ref": "x-cluster.spec.json" - }, "version": { "type": "string", - "description": "Version of the Compose specification used. Tools not implementing required version MUST reject the configuration file." + "description": "declared for backward compatibility, ignored." + }, + + "name": { + "type": "string", + "pattern": "^[a-z0-9][a-z0-9_-]*$", + "description": "define the Compose project name, until user defines one explicitly." + }, + + "include": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/include" + }, + "description": "compose sub-projects to be included." }, + "services": { "id": "#/properties/services", "type": "object", @@ -26,6 +36,7 @@ }, "additionalProperties": false }, + "networks": { "id": "#/properties/networks", "type": "object", @@ -35,6 +46,7 @@ } } }, + "volumes": { "id": "#/properties/volumes", "type": "object", @@ -45,6 +57,7 @@ }, "additionalProperties": false }, + "secrets": { "id": "#/properties/secrets", "type": "object", @@ -55,6 +68,7 @@ }, "additionalProperties": false }, + "configs": { "id": "#/properties/configs", "type": "object", @@ -66,106 +80,51 @@ "additionalProperties": false } }, - "patternProperties": { - "^x-": {} - }, + + "patternProperties": {"^x-": {}}, "additionalProperties": false, + "definitions": { + "service": { "id": "#/definitions/service", "type": "object", + "properties": { - "x-iam": { - "$ref": "services.x-iam.spec.json" - }, - "x-network": { - "$ref": "services.x-network.spec.json" - }, - "x-scaling": { - "$ref": "services.x-scaling.spec.json" - }, - "x-alarms": { - "$ref": "services.x-alarms.spec.json" - }, - "x-logging": { - "$ref": "services.x-logging.spec.json" - }, - "x-xray": { - "type": "boolean" - }, - "x-codeguru_profiler": { - "$ref": "services.x-codeguru_profiler.spec.json" - }, - "x-ecr": { - "$ref": "services.x-ecr.spec.json" - }, - "x-docker_opts": { - "$ref": "services.x-docker_opts.spec.json" - }, - "x-environment": { - "$ref": "services.x-environment.spec.json" - }, - "x-prometheus": { - "$ref": "services.x-prometheus.spec.json" - }, - "x-monitoring": { - "$ref": "services.x-monitoring.spec.json" - }, - "x-ecs": { - "$ref": "services.x-ecs.spec.json" - }, - "deploy": { - "$ref": "#/definitions/deployment" - }, + "develop": {"$ref": "#/definitions/development"}, + "deploy": {"$ref": "#/definitions/deployment"}, + "annotations": {"$ref": "#/definitions/list_or_dict"}, + "attach": {"type": "boolean"}, "build": { "oneOf": [ - { - "type": "string" - }, + {"type": "string"}, { "type": "object", "properties": { - "context": { - "type": "string" - }, - "dockerfile": { - "type": "string" - }, - "args": { - "$ref": "#/definitions/list_or_dict" - }, - "labels": { - "$ref": "#/definitions/list_or_dict" - }, - "cache_from": { - "type": "array", - "items": { - "type": "string" - } - }, - "network": { - "type": "string" - }, - "target": { - "type": "string" - }, - "shm_size": { - "type": [ - "integer", - "string" - ] - }, - "extra_hosts": { - "$ref": "#/definitions/list_or_dict" - }, - "isolation": { - "type": "string" - } + "context": {"type": "string"}, + "dockerfile": {"type": "string"}, + "dockerfile_inline": {"type": "string"}, + "args": {"$ref": "#/definitions/list_or_dict"}, + "ssh": {"$ref": "#/definitions/list_or_dict"}, + "labels": {"$ref": "#/definitions/list_or_dict"}, + "cache_from": {"type": "array", "items": {"type": "string"}}, + "cache_to": {"type": "array", "items": {"type": "string"}}, + "no_cache": {"type": "boolean"}, + "additional_contexts": {"$ref": "#/definitions/list_or_dict"}, + "network": {"type": "string"}, + "pull": {"type": "boolean"}, + "target": {"type": "string"}, + "shm_size": {"type": ["integer", "string"]}, + "extra_hosts": {"$ref": "#/definitions/list_or_dict"}, + "isolation": {"type": "string"}, + "privileged": {"type": "boolean"}, + "secrets": {"$ref": "#/definitions/service_config_or_secret"}, + "tags": {"type": "array", "items": {"type": "string"}}, + "ulimits": {"$ref": "#/definitions/ulimits"}, + "platforms": {"type": "array", "items": {"type": "string"}} }, "additionalProperties": false, - "patternProperties": { - "^x-": {} - } + "patternProperties": {"^x-": {}} } ] }, @@ -174,178 +133,57 @@ "properties": { "device_read_bps": { "type": "array", - "items": { - "$ref": "#/definitions/blkio_limit" - } + "items": {"$ref": "#/definitions/blkio_limit"} }, "device_read_iops": { "type": "array", - "items": { - "$ref": "#/definitions/blkio_limit" - } + "items": {"$ref": "#/definitions/blkio_limit"} }, "device_write_bps": { "type": "array", - "items": { - "$ref": "#/definitions/blkio_limit" - } + "items": {"$ref": "#/definitions/blkio_limit"} }, "device_write_iops": { "type": "array", - "items": { - "$ref": "#/definitions/blkio_limit" - } - }, - "weight": { - "type": "integer" + "items": {"$ref": "#/definitions/blkio_limit"} }, + "weight": {"type": "integer"}, "weight_device": { "type": "array", - "items": { - "$ref": "#/definitions/blkio_weight" - } + "items": {"$ref": "#/definitions/blkio_weight"} } }, "additionalProperties": false }, - "cap_add": { - "type": "array", - "items": { - "type": "string" - }, - "uniqueItems": true - }, - "cap_drop": { - "type": "array", - "items": { - "type": "string" - }, - "uniqueItems": true - }, - "cgroup_parent": { - "type": "string" - }, - "command": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "array", - "items": { - "type": "string" - } - } - ] - }, - "configs": { - "type": "array", - "items": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "object", - "properties": { - "source": { - "type": "string" - }, - "target": { - "type": "string" - }, - "uid": { - "type": "string" - }, - "gid": { - "type": "string" - }, - "mode": { - "type": "number" - } - }, - "additionalProperties": false, - "patternProperties": { - "^x-": {} - } - } - ] - } - }, - "container_name": { - "type": "string" - }, - "cpu_count": { - "type": "integer", - "minimum": 0 - }, - "cpu_percent": { - "type": "integer", - "minimum": 0, - "maximum": 100 - }, - "cpu_shares": { - "type": [ - "number", - "string" - ] - }, - "cpu_quota": { - "type": [ - "number", - "string" - ] - }, - "cpu_period": { - "type": [ - "number", - "string" - ] - }, - "cpu_rt_period": { - "type": [ - "number", - "string" - ] - }, - "cpu_rt_runtime": { - "type": [ - "number", - "string" - ] - }, - "cpus": { - "type": [ - "number", - "string" - ] - }, - "cpuset": { - "type": "string" - }, + "cap_add": {"type": "array", "items": {"type": "string"}, "uniqueItems": true}, + "cap_drop": {"type": "array", "items": {"type": "string"}, "uniqueItems": true}, + "cgroup": {"type": "string", "enum": ["host", "private"]}, + "cgroup_parent": {"type": "string"}, + "command": {"$ref": "#/definitions/command"}, + "configs": {"$ref": "#/definitions/service_config_or_secret"}, + "container_name": {"type": "string"}, + "cpu_count": {"type": "integer", "minimum": 0}, + "cpu_percent": {"type": "integer", "minimum": 0, "maximum": 100}, + "cpu_shares": {"type": ["number", "string"]}, + "cpu_quota": {"type": ["number", "string"]}, + "cpu_period": {"type": ["number", "string"]}, + "cpu_rt_period": {"type": ["number", "string"]}, + "cpu_rt_runtime": {"type": ["number", "string"]}, + "cpus": {"type": ["number", "string"]}, + "cpuset": {"type": "string"}, "credential_spec": { "type": "object", "properties": { - "config": { - "type": "string" - }, - "file": { - "type": "string" - }, - "registry": { - "type": "string" - } + "config": {"type": "string"}, + "file": {"type": "string"}, + "registry": {"type": "string"} }, "additionalProperties": false, - "patternProperties": { - "^x-": {} - } + "patternProperties": {"^x-": {}} }, "depends_on": { "oneOf": [ - { - "$ref": "#/definitions/list_of_strings" - }, + {"$ref": "#/definitions/list_of_strings"}, { "type": "object", "additionalProperties": false, @@ -354,205 +192,96 @@ "type": "object", "additionalProperties": false, "properties": { + "restart": {"type": "boolean"}, + "required": { + "type": "boolean", + "default": true + }, "condition": { "type": "string", - "enum": [ - "service_started", - "service_healthy", - "service_completed_successfully" - ] + "enum": ["service_started", "service_healthy", "service_completed_successfully"] } }, - "required": [ - "condition" - ] + "required": ["condition"] } } } ] }, - "device_cgroup_rules": { - "$ref": "#/definitions/list_of_strings" - }, - "devices": { - "type": "array", - "items": { - "type": "string" - }, - "uniqueItems": true - }, - "dns": { - "$ref": "#/definitions/string_or_list" - }, - "dns_opt": { - "type": "array", - "items": { - "type": "string" - }, - "uniqueItems": true - }, - "dns_search": { - "$ref": "#/definitions/string_or_list" - }, - "domainname": { - "type": "string" - }, - "entrypoint": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "array", - "items": { - "type": "string" - } - } - ] - }, - "env_file": { - "$ref": "#/definitions/string_or_list" - }, - "environment": { - "$ref": "#/definitions/list_or_dict" - }, + "device_cgroup_rules": {"$ref": "#/definitions/list_of_strings"}, + "devices": {"type": "array", "items": {"type": "string"}, "uniqueItems": true}, + "dns": {"$ref": "#/definitions/string_or_list"}, + "dns_opt": {"type": "array","items": {"type": "string"}, "uniqueItems": true}, + "dns_search": {"$ref": "#/definitions/string_or_list"}, + "domainname": {"type": "string"}, + "entrypoint": {"$ref": "#/definitions/command"}, + "env_file": {"$ref": "#/definitions/env_file"}, + "environment": {"$ref": "#/definitions/list_or_dict"}, + "expose": { "type": "array", "items": { - "type": [ - "string", - "number" - ], + "type": ["string", "number"], "format": "expose" }, "uniqueItems": true }, "extends": { "oneOf": [ - { - "type": "string" - }, + {"type": "string"}, { "type": "object", + "properties": { - "service": { - "type": "string" - }, - "file": { - "type": "string" - } + "service": {"type": "string"}, + "file": {"type": "string"} }, - "required": [ - "service" - ], + "required": ["service"], "additionalProperties": false } ] }, - "external_links": { - "type": "array", - "items": { - "type": "string" - }, - "uniqueItems": true - }, - "extra_hosts": { - "$ref": "#/definitions/list_or_dict" - }, + "external_links": {"type": "array", "items": {"type": "string"}, "uniqueItems": true}, + "extra_hosts": {"$ref": "#/definitions/list_or_dict"}, "group_add": { "type": "array", "items": { - "type": [ - "string", - "number" - ] - }, - "uniqueItems": true - }, - "healthcheck": { - "$ref": "#/definitions/healthcheck" - }, - "hostname": { - "type": "string" - }, - "image": { - "type": "string" - }, - "init": { - "type": "boolean" - }, - "ipc": { - "type": "string" - }, - "isolation": { - "type": "string" - }, - "labels": { - "$ref": "#/definitions/list_or_dict" - }, - "links": { - "type": "array", - "items": { - "type": "string" + "type": ["string", "number"] }, "uniqueItems": true }, + "healthcheck": {"$ref": "#/definitions/healthcheck"}, + "hostname": {"type": "string"}, + "image": {"type": "string"}, + "init": {"type": "boolean"}, + "ipc": {"type": "string"}, + "isolation": {"type": "string"}, + "labels": {"$ref": "#/definitions/list_or_dict"}, + "links": {"type": "array", "items": {"type": "string"}, "uniqueItems": true}, "logging": { "type": "object", + "properties": { - "driver": { - "type": "string" - }, + "driver": {"type": "string"}, "options": { "type": "object", "patternProperties": { - "^.+$": { - "type": [ - "string", - "number", - "null" - ] - } + "^.+$": {"type": ["string", "number", "null"]} } } }, "additionalProperties": false, - "patternProperties": { - "^x-": {} - } - }, - "mac_address": { - "type": "string" - }, - "mem_limit": { - "type": [ - "number", - "string" - ] - }, - "mem_reservation": { - "type": [ - "string", - "integer" - ] - }, - "mem_swappiness": { - "type": "integer" - }, - "memswap_limit": { - "type": [ - "number", - "string" - ] - }, - "network_mode": { - "type": "string" - }, + "patternProperties": {"^x-": {}} + }, + "mac_address": {"type": "string"}, + "mem_limit": {"type": ["number", "string"]}, + "mem_reservation": {"type": ["string", "integer"]}, + "mem_swappiness": {"type": "integer"}, + "memswap_limit": {"type": ["number", "string"]}, + "network_mode": {"type": "string"}, "networks": { "oneOf": [ - { - "$ref": "#/definitions/list_of_strings" - }, + {"$ref": "#/definitions/list_of_strings"}, { "type": "object", "patternProperties": { @@ -561,30 +290,17 @@ { "type": "object", "properties": { - "aliases": { - "$ref": "#/definitions/list_of_strings" - }, - "ipv4_address": { - "type": "string" - }, - "ipv6_address": { - "type": "string" - }, - "link_local_ips": { - "$ref": "#/definitions/list_of_strings" - }, - "priority": { - "type": "number" - } + "aliases": {"$ref": "#/definitions/list_of_strings"}, + "ipv4_address": {"type": "string"}, + "ipv6_address": {"type": "string"}, + "link_local_ips": {"$ref": "#/definitions/list_of_strings"}, + "mac_address": {"type": "string"}, + "priority": {"type": "number"} }, "additionalProperties": false, - "patternProperties": { - "^x-": {} - } + "patternProperties": {"^x-": {}} }, - { - "type": "null" - } + {"type": "null"} ] } }, @@ -592,272 +308,110 @@ } ] }, - "oom_kill_disable": { - "type": "boolean" - }, - "oom_score_adj": { - "type": "integer", - "minimum": -1000, - "maximum": 1000 - }, - "pid": { - "type": [ - "string", - "null" - ] - }, - "pids_limit": { - "type": [ - "number", - "string" - ] - }, - "platform": { - "type": "string" - }, + "oom_kill_disable": {"type": "boolean"}, + "oom_score_adj": {"type": "integer", "minimum": -1000, "maximum": 1000}, + "pid": {"type": ["string", "null"]}, + "pids_limit": {"type": ["number", "string"]}, + "platform": {"type": "string"}, "ports": { "type": "array", "items": { "oneOf": [ - { - "type": "number", - "format": "ports" - }, - { - "type": "string", - "format": "ports" - }, + {"type": "number", "format": "ports"}, + {"type": "string", "format": "ports"}, { "type": "object", "properties": { - "mode": { - "type": "string" - }, - "target": { - "type": "integer" - }, - "published": { - "type": "integer" - }, - "protocol": { - "type": "string" - } + "name": {"type": "string"}, + "mode": {"type": "string"}, + "host_ip": {"type": "string"}, + "target": {"type": "integer"}, + "published": {"type": ["string", "integer"]}, + "protocol": {"type": "string"} }, "additionalProperties": false, - "patternProperties": { - "^x-": {} - } + "patternProperties": {"^x-": {}} } ] }, "uniqueItems": true }, - "privileged": { - "type": "boolean" - }, - "profiles": { - "$ref": "#/definitions/list_of_strings" - }, - "pull_policy": { - "type": "string", - "enum": [ - "always", - "never", - "if_not_present", - "build" - ] - }, - "read_only": { - "type": "boolean" - }, - "restart": { - "type": "string" - }, + "privileged": {"type": "boolean"}, + "profiles": {"$ref": "#/definitions/list_of_strings"}, + "pull_policy": {"type": "string", "enum": [ + "always", "never", "if_not_present", "build", "missing" + ]}, + "read_only": {"type": "boolean"}, + "restart": {"type": "string"}, "runtime": { "type": "string" }, "scale": { "type": "integer" }, - "security_opt": { - "type": "array", - "items": { - "type": "string" - }, - "uniqueItems": true - }, - "shm_size": { - "type": [ - "number", - "string" - ] - }, - "secrets": { - "type": "array", - "items": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "object", - "properties": { - "source": { - "type": "string" - }, - "target": { - "type": "string" - }, - "uid": { - "type": "string" - }, - "gid": { - "type": "string" - }, - "mode": { - "type": "number" - } - }, - "additionalProperties": false, - "patternProperties": { - "^x-": {} - } - } - ] - } - }, - "sysctls": { - "$ref": "#/definitions/list_or_dict" - }, - "stdin_open": { - "type": "boolean" - }, - "stop_grace_period": { - "type": "string", - "format": "duration" - }, - "stop_signal": { - "type": "string" - }, - "storage_opt": { - "type": "object" - }, - "tmpfs": { - "$ref": "#/definitions/string_or_list" - }, - "tty": { - "type": "boolean" - }, - "ulimits": { - "type": "object", - "patternProperties": { - "^[a-z]+$": { - "oneOf": [ - { - "type": "integer" - }, - { - "type": "object", - "properties": { - "hard": { - "type": "integer" - }, - "soft": { - "type": "integer" - } - }, - "required": [ - "soft", - "hard" - ], - "additionalProperties": false, - "patternProperties": { - "^x-": {} - } - } - ] - } - } - }, - "user": { - "type": "string" - }, - "userns_mode": { - "type": "string" - }, - "volumes": { - "type": "array", - "items": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "object", - "required": [ - "type" - ], - "properties": { - "type": { - "type": "string" - }, - "source": { - "type": "string" - }, - "target": { - "type": "string" - }, - "read_only": { - "type": "boolean" - }, - "consistency": { - "type": "string" - }, + "security_opt": {"type": "array", "items": {"type": "string"}, "uniqueItems": true}, + "shm_size": {"type": ["number", "string"]}, + "secrets": {"$ref": "#/definitions/service_config_or_secret"}, + "sysctls": {"$ref": "#/definitions/list_or_dict"}, + "stdin_open": {"type": "boolean"}, + "stop_grace_period": {"type": "string", "format": "duration"}, + "stop_signal": {"type": "string"}, + "storage_opt": {"type": "object"}, + "tmpfs": {"$ref": "#/definitions/string_or_list"}, + "tty": {"type": "boolean"}, + "ulimits": {"$ref": "#/definitions/ulimits"}, + "user": {"type": "string"}, + "uts": {"type": "string"}, + "userns_mode": {"type": "string"}, + "volumes": { + "type": "array", + "items": { + "oneOf": [ + {"type": "string"}, + { + "type": "object", + "required": ["type"], + "properties": { + "type": {"type": "string"}, + "source": {"type": "string"}, + "target": {"type": "string"}, + "read_only": {"type": "boolean"}, + "consistency": {"type": "string"}, "bind": { "type": "object", "properties": { - "propagation": { - "type": "string" - }, - "create_host_path": { - "type": "boolean" - } + "propagation": {"type": "string"}, + "create_host_path": {"type": "boolean"}, + "selinux": {"type": "string", "enum": ["z", "Z"]} }, "additionalProperties": false, - "patternProperties": { - "^x-": {} - } + "patternProperties": {"^x-": {}} }, "volume": { "type": "object", "properties": { - "nocopy": { - "type": "boolean" - } + "nocopy": {"type": "boolean"} }, "additionalProperties": false, - "patternProperties": { - "^x-": {} - } + "patternProperties": {"^x-": {}} }, "tmpfs": { "type": "object", "properties": { "size": { - "type": "integer", - "minimum": 0 - } + "oneOf": [ + {"type": "integer", "minimum": 0}, + {"type": "string"} + ] + }, + "mode": {"type": "number"} }, "additionalProperties": false, - "patternProperties": { - "^x-": {} - } + "patternProperties": {"^x-": {}} } }, "additionalProperties": false, - "patternProperties": { - "^x-": {} - } + "patternProperties": {"^x-": {}} } ] }, @@ -865,144 +419,93 @@ }, "volumes_from": { "type": "array", - "items": { - "type": "string" - }, + "items": {"type": "string"}, "uniqueItems": true }, - "working_dir": { - "type": "string" - } - }, - "patternProperties": { - "^x-": {} + "working_dir": {"type": "string"} }, + "patternProperties": {"^x-": {}}, "additionalProperties": false }, + "healthcheck": { "id": "#/definitions/healthcheck", "type": "object", "properties": { - "disable": { - "type": "boolean" - }, - "interval": { - "type": "string", - "format": "duration" - }, - "retries": { - "type": "number" - }, + "disable": {"type": "boolean"}, + "interval": {"type": "string", "format": "duration"}, + "retries": {"type": "number"}, "test": { "oneOf": [ - { - "type": "string" - }, - { - "type": "array", - "items": { - "type": "string" - } - } + {"type": "string"}, + {"type": "array", "items": {"type": "string"}} ] }, - "timeout": { - "type": "string" - }, - "start_period": { - "type": "string", - "format": "duration" - } + "timeout": {"type": "string", "format": "duration"}, + "start_period": {"type": "string", "format": "duration"}, + "start_interval": {"type": "string", "format": "duration"} }, "additionalProperties": false, - "patternProperties": { - "^x-": {} + "patternProperties": {"^x-": {}} + }, + "development": { + "id": "#/definitions/development", + "type": ["object", "null"], + "properties": { + "watch": { + "type": "array", + "items": { + "type": "object", + "properties": { + "ignore": {"type": "array", "items": {"type": "string"}}, + "path": {"type": "string"}, + "action": {"type": "string", "enum": ["rebuild", "sync", "sync+restart"]}, + "target": {"type": "string"} + } + }, + "required": ["path", "action"], + "additionalProperties": false, + "patternProperties": {"^x-": {}} + } } }, "deployment": { "id": "#/definitions/deployment", - "type": [ - "object", - "null" - ], + "type": ["object", "null"], "properties": { - "mode": { - "type": "string" - }, - "endpoint_mode": { - "type": "string" - }, - "replicas": { - "type": "integer" - }, - "labels": { - "$ref": "#/definitions/list_or_dict" - }, + "mode": {"type": "string"}, + "endpoint_mode": {"type": "string"}, + "replicas": {"type": "integer"}, + "labels": {"$ref": "#/definitions/list_or_dict"}, "rollback_config": { "type": "object", "properties": { - "parallelism": { - "type": "integer" - }, - "delay": { - "type": "string", - "format": "duration" - }, - "failure_action": { - "type": "string" - }, - "monitor": { - "type": "string", - "format": "duration" - }, - "max_failure_ratio": { - "type": "number" - }, - "order": { - "type": "string", - "enum": [ - "start-first", - "stop-first" - ] - } + "parallelism": {"type": "integer"}, + "delay": {"type": "string", "format": "duration"}, + "failure_action": {"type": "string"}, + "monitor": {"type": "string", "format": "duration"}, + "max_failure_ratio": {"type": "number"}, + "order": {"type": "string", "enum": [ + "start-first", "stop-first" + ]} }, "additionalProperties": false, - "patternProperties": { - "^x-": {} - } + "patternProperties": {"^x-": {}} }, "update_config": { "type": "object", "properties": { - "parallelism": { - "type": "integer" - }, - "delay": { - "type": "string", - "format": "duration" - }, - "failure_action": { - "type": "string" - }, - "monitor": { - "type": "string", - "format": "duration" - }, - "max_failure_ratio": { - "type": "number" - }, - "order": { - "type": "string", - "enum": [ - "start-first", - "stop-first" - ] - } + "parallelism": {"type": "integer"}, + "delay": {"type": "string", "format": "duration"}, + "failure_action": {"type": "string"}, + "monitor": {"type": "string", "format": "duration"}, + "max_failure_ratio": {"type": "number"}, + "order": {"type": "string", "enum": [ + "start-first", "stop-first" + ]} }, "additionalProperties": false, - "patternProperties": { - "^x-": {} - } + "patternProperties": {"^x-": {}} }, "resources": { "type": "object", @@ -1010,113 +513,64 @@ "limits": { "type": "object", "properties": { - "cpus": { - "type": [ - "number", - "string" - ] - }, - "memory": { - "type": "string" - } + "cpus": {"type": ["number", "string"]}, + "memory": {"type": "string"}, + "pids": {"type": "integer"} }, "additionalProperties": false, - "patternProperties": { - "^x-": {} - } + "patternProperties": {"^x-": {}} }, "reservations": { "type": "object", "properties": { - "cpus": { - "type": [ - "number", - "string" - ] - }, - "memory": { - "type": "string" - }, - "generic_resources": { - "$ref": "#/definitions/generic_resources" - }, - "devices": { - "$ref": "#/definitions/devices" - } + "cpus": {"type": ["number", "string"]}, + "memory": {"type": "string"}, + "generic_resources": {"$ref": "#/definitions/generic_resources"}, + "devices": {"$ref": "#/definitions/devices"} }, "additionalProperties": false, - "patternProperties": { - "^x-": {} - } + "patternProperties": {"^x-": {}} } }, "additionalProperties": false, - "patternProperties": { - "^x-": {} - } + "patternProperties": {"^x-": {}} }, "restart_policy": { "type": "object", "properties": { - "condition": { - "type": "string" - }, - "delay": { - "type": "string", - "format": "duration" - }, - "max_attempts": { - "type": "integer" - }, - "window": { - "type": "string", - "format": "duration" - } + "condition": {"type": "string"}, + "delay": {"type": "string", "format": "duration"}, + "max_attempts": {"type": "integer"}, + "window": {"type": "string", "format": "duration"} }, "additionalProperties": false, - "patternProperties": { - "^x-": {} - } + "patternProperties": {"^x-": {}} }, "placement": { "type": "object", "properties": { - "constraints": { - "type": "array", - "items": { - "type": "string" - } - }, + "constraints": {"type": "array", "items": {"type": "string"}}, "preferences": { "type": "array", "items": { "type": "object", "properties": { - "spread": { - "type": "string" - } + "spread": {"type": "string"} }, "additionalProperties": false, - "patternProperties": { - "^x-": {} - } + "patternProperties": {"^x-": {}} } }, - "max_replicas_per_node": { - "type": "integer" - } + "max_replicas_per_node": {"type": "integer"} }, "additionalProperties": false, - "patternProperties": { - "^x-": {} - } + "patternProperties": {"^x-": {}} } }, "additionalProperties": false, - "patternProperties": { - "^x-": {} - } + "patternProperties": {"^x-": {}} }, + "generic_resources": { "id": "#/definitions/generic_resources", "type": "array", @@ -1126,137 +580,96 @@ "discrete_resource_spec": { "type": "object", "properties": { - "kind": { - "type": "string" - }, - "value": { - "type": "number" - } + "kind": {"type": "string"}, + "value": {"type": "number"} }, "additionalProperties": false, - "patternProperties": { - "^x-": {} - } + "patternProperties": {"^x-": {}} } }, "additionalProperties": false, - "patternProperties": { - "^x-": {} - } + "patternProperties": {"^x-": {}} } }, + "devices": { "id": "#/definitions/devices", "type": "array", "items": { "type": "object", "properties": { - "capabilities": { - "$ref": "#/definitions/list_of_strings" - }, - "count": { - "type": [ - "string", - "integer" - ] - }, - "device_ids": { - "$ref": "#/definitions/list_of_strings" - }, - "driver": { - "type": "string" - }, - "options": { - "$ref": "#/definitions/list_or_dict" - } + "capabilities": {"$ref": "#/definitions/list_of_strings"}, + "count": {"type": ["string", "integer"]}, + "device_ids": {"$ref": "#/definitions/list_of_strings"}, + "driver":{"type": "string"}, + "options":{"$ref": "#/definitions/list_or_dict"} }, "additionalProperties": false, - "patternProperties": { - "^x-": {} - } + "patternProperties": {"^x-": {}} } }, + + "include": { + "id": "#/definitions/include", + "oneOf": [ + {"type": "string"}, + { + "type": "object", + "properties": { + "path": {"$ref": "#/definitions/string_or_list"}, + "env_file": {"$ref": "#/definitions/string_or_list"}, + "project_directory": {"type": "string"} + }, + "additionalProperties": false + } + ] + }, + "network": { "id": "#/definitions/network", - "type": [ - "object", - "null" - ], + "type": ["object", "null"], "properties": { - "name": { - "type": "string" - }, - "driver": { - "type": "string" - }, + "name": {"type": "string"}, + "driver": {"type": "string"}, "driver_opts": { "type": "object", "patternProperties": { - "^.+$": { - "type": [ - "string", - "number" - ] - } + "^.+$": {"type": ["string", "number"]} } }, "ipam": { "type": "object", "properties": { - "driver": { - "type": "string" - }, + "driver": {"type": "string"}, "config": { "type": "array", "items": { "type": "object", "properties": { - "subnet": { - "type": "string", - "format": "subnet_ip_address" - }, - "ip_range": { - "type": "string" - }, - "gateway": { - "type": "string" - }, + "subnet": {"type": "string", "format": "subnet_ip_address"}, + "ip_range": {"type": "string"}, + "gateway": {"type": "string"}, "aux_addresses": { "type": "object", "additionalProperties": false, - "patternProperties": { - "^.+$": { - "type": "string" - } - } + "patternProperties": {"^.+$": {"type": "string"}} } }, "additionalProperties": false, - "patternProperties": { - "^x-": {} - } + "patternProperties": {"^x-": {}} } }, "options": { "type": "object", "additionalProperties": false, - "patternProperties": { - "^.+$": { - "type": "string" - } - } + "patternProperties": {"^.+$": {"type": "string"}} } }, "additionalProperties": false, - "patternProperties": { - "^x-": {} - } + "patternProperties": {"^x-": {}} }, "external": { - "type": [ - "boolean", - "object" - ], + "type": ["boolean", "object"], "properties": { "name": { "deprecated": true, @@ -1264,57 +677,31 @@ } }, "additionalProperties": false, - "patternProperties": { - "^x-": {} - } - }, - "internal": { - "type": "boolean" + "patternProperties": {"^x-": {}} }, - "enable_ipv6": { - "type": "boolean" - }, - "attachable": { - "type": "boolean" - }, - "labels": { - "$ref": "#/definitions/list_or_dict" - } + "internal": {"type": "boolean"}, + "enable_ipv6": {"type": "boolean"}, + "attachable": {"type": "boolean"}, + "labels": {"$ref": "#/definitions/list_or_dict"} }, "additionalProperties": false, - "patternProperties": { - "^x-": {} - } + "patternProperties": {"^x-": {}} }, + "volume": { "id": "#/definitions/volume", - "type": [ - "object", - "null" - ], + "type": ["object", "null"], "properties": { - "name": { - "type": "string" - }, - "driver": { - "type": "string" - }, + "name": {"type": "string"}, + "driver": {"type": "string"}, "driver_opts": { "type": "object", "patternProperties": { - "^.+$": { - "type": [ - "string", - "number" - ] - } + "^.+$": {"type": ["string", "number"]} } }, "external": { - "type": [ - "boolean", - "object" - ], + "type": ["boolean", "object"], "properties": { "name": { "deprecated": true, @@ -1322,84 +709,51 @@ } }, "additionalProperties": false, - "patternProperties": { - "^x-": {} - } + "patternProperties": {"^x-": {}} }, - "labels": { - "$ref": "#/definitions/list_or_dict" - } + "labels": {"$ref": "#/definitions/list_or_dict"} }, "additionalProperties": false, - "patternProperties": { - "^x-": {} - } + "patternProperties": {"^x-": {}} }, + "secret": { "id": "#/definitions/secret", "type": "object", "properties": { - "name": { - "type": "string" - }, - "file": { - "type": "string" - }, + "name": {"type": "string"}, + "environment": {"type": "string"}, + "file": {"type": "string"}, "external": { - "type": [ - "boolean", - "object" - ], + "type": ["boolean", "object"], "properties": { - "name": { - "type": "string" - } + "name": {"type": "string"} } }, - "labels": { - "$ref": "#/definitions/list_or_dict" - }, - "driver": { - "type": "string" - }, + "labels": {"$ref": "#/definitions/list_or_dict"}, + "driver": {"type": "string"}, "driver_opts": { "type": "object", "patternProperties": { - "^.+$": { - "type": [ - "string", - "number" - ] - } + "^.+$": {"type": ["string", "number"]} } }, - "template_driver": { - "type": "string" - }, - "x-secrets": { - "$ref": "secrets.x-secrets.spec.json" - } + "template_driver": {"type": "string"} }, "additionalProperties": false, - "patternProperties": { - "^x-": {} - } + "patternProperties": {"^x-": {}} }, + "config": { "id": "#/definitions/config", "type": "object", "properties": { - "name": { - "type": "string" - }, - "file": { - "type": "string" - }, + "name": {"type": "string"}, + "content": {"type": "string"}, + "environment": {"type": "string"}, + "file": {"type": "string"}, "external": { - "type": [ - "boolean", - "object" - ], + "type": ["boolean", "object"], "properties": { "name": { "deprecated": true, @@ -1407,106 +761,145 @@ } } }, - "labels": { - "$ref": "#/definitions/list_or_dict" - }, - "template_driver": { - "type": "string" - } + "labels": {"$ref": "#/definitions/list_or_dict"}, + "template_driver": {"type": "string"} }, "additionalProperties": false, - "patternProperties": { - "^x-": {} - } + "patternProperties": {"^x-": {}} }, - "string_or_list": { + + "command": { "oneOf": [ + {"type": "null"}, + {"type": "string"}, + {"type": "array","items": {"type": "string"}} + ] + }, + + "env_file": { + "oneOf": [ + {"type": "string"}, { - "type": "string" - }, - { - "$ref": "#/definitions/list_of_strings" + "type": "array", + "items": { + "oneOf": [ + {"type": "string"}, + { + "type": "object", + "additionalProperties": false, + "properties": { + "path": { + "type": "string" + }, + "required": { + "type": "boolean", + "default": true + } + }, + "required": [ + "path" + ] + } + ] + } } ] }, + + "string_or_list": { + "oneOf": [ + {"type": "string"}, + {"$ref": "#/definitions/list_of_strings"} + ] + }, + "list_of_strings": { "type": "array", - "items": { - "type": "string" - }, + "items": {"type": "string"}, "uniqueItems": true }, + "list_or_dict": { "oneOf": [ { "type": "object", "patternProperties": { ".+": { - "type": [ - "string", - "number", - "null" - ] + "type": ["string", "number", "boolean", "null"] } }, "additionalProperties": false }, - { - "type": "array", - "items": { - "type": "string" - }, - "uniqueItems": true - } + {"type": "array", "items": {"type": "string"}, "uniqueItems": true} ] }, + "blkio_limit": { "type": "object", "properties": { - "path": { - "type": "string" - }, - "rate": { - "type": [ - "integer", - "string" - ] - } + "path": {"type": "string"}, + "rate": {"type": ["integer", "string"]} }, "additionalProperties": false }, "blkio_weight": { "type": "object", "properties": { - "path": { - "type": "string" - }, - "weight": { - "type": "integer" - } + "path": {"type": "string"}, + "weight": {"type": "integer"} }, "additionalProperties": false }, + "service_config_or_secret": { + "type": "array", + "items": { + "oneOf": [ + {"type": "string"}, + { + "type": "object", + "properties": { + "source": {"type": "string"}, + "target": {"type": "string"}, + "uid": {"type": "string"}, + "gid": {"type": "string"}, + "mode": {"type": "number"} + }, + "additionalProperties": false, + "patternProperties": {"^x-": {}} + } + ] + } + }, + "ulimits": { + "type": "object", + "patternProperties": { + "^[a-z]+$": { + "oneOf": [ + {"type": "integer"}, + { + "type": "object", + "properties": { + "hard": {"type": "integer"}, + "soft": {"type": "integer"} + }, + "required": ["soft", "hard"], + "additionalProperties": false, + "patternProperties": {"^x-": {}} + } + ] + } + } + }, "constraints": { "service": { "id": "#/definitions/constraints/service", "anyOf": [ - { - "required": [ - "build" - ] - }, - { - "required": [ - "image" - ] - } + {"required": ["build"]}, + {"required": ["image"]} ], "properties": { "build": { - "required": [ - "context" - ] + "required": ["context"] } } } diff --git a/ecs_composex/specs/services.x-network.spec.json b/ecs_composex/specs/services.x-network.spec.json index fcac668a7..53551c492 100644 --- a/ecs_composex/specs/services.x-network.spec.json +++ b/ecs_composex/specs/services.x-network.spec.json @@ -8,7 +8,8 @@ "additionalProperties": false, "properties": { "AssignPublicIp": { - "type": "boolean" + "type": "boolean", + "description": "Enables assigning a public IP address to the service tasks." }, "AdditionalSecurityGroups": { "description": "Define additional security groups to use.", @@ -20,7 +21,8 @@ "oneOf": [ { "type": "string", - "description": "The security group ID to use. It must belong to the same VPC you are deploying to" + "description": "The security group ID to use. It must belong to the same VPC you are deploying to", + "pattern": "^sg-[a-z0-9]+$" }, { "$ref": "x-resources.common.spec.json#/definitions/Lookup" @@ -29,6 +31,55 @@ } } }, + "x-ecs_connect": { + "type": "object", + "oneOf": [ + { + "required": [ + "Properties" + ] + }, + { + "required": [ + "MacroParameters" + ] + } + ], + "properties": { + "Properties": { + "type": "object", + "description": "Literal properties to set as in https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ecs-service-serviceconnectconfiguration.html" + }, + "MacroParameters": { + "type": "object", + "description": "ECS Compose-X Shorthand syntax to configure ECS Connect.", + "additionalProperties": false, + "properties": { + "ServiceAlias": { + "type": "string", + "description": "DNS Alias to use for this service with ECS Connect" + }, + "ServicePort": { + "type": "number", + "minimum": 0, + "maximum": 65535, + "description": "The port to use for registration. If not set, uses the first port in the ports list" + }, + "ServicePortName": { + "type": "string", + "description": "Name of the port. Must be the same as ports[].name. If not specified, uses generated port name of the first port." + }, + "x-cloudmap": { + "type": "string", + "description": "Name of the namespace defined in the x-cloudmap root level to use." + } + }, + "patternProperties": { + "x-*": {} + } + } + } + }, "x-cloudmap": { "oneOf": [ { diff --git a/poetry.lock b/poetry.lock index de8612a21..b8f88edc6 100644 --- a/poetry.lock +++ b/poetry.lock @@ -805,18 +805,18 @@ python-dateutil = ">=2.8.2,<3.0.0" [[package]] name = "compose-x-render" -version = "1.0.0" +version = "1.1.0" description = "Library & Tool to compile/merge compose files with top level extension fields" optional = false python-versions = ">=3.9,<4.0" files = [ - {file = "compose_x_render-1.0.0-py3-none-any.whl", hash = "sha256:deb6aef1535a87cc85cc185185ce3996a85a9002e0e157c9416dab4138bf489d"}, - {file = "compose_x_render-1.0.0.tar.gz", hash = "sha256:de93bec0a07122f67fe40676d8d31152a2ba27ff5d5bae15aca450d569938dc9"}, + {file = "compose_x_render-1.1.0-py3-none-any.whl", hash = "sha256:87344c00d983761a929710db3e35696717a32dcb9813cbd99d6946a74a456e5b"}, + {file = "compose_x_render-1.1.0.tar.gz", hash = "sha256:569c41b488bb9b98bbea798f37ec230c7ca077cf8c741dbfdf463d3fa68bd54c"}, ] [package.dependencies] argparse = ">=1.4.0,<2.0.0" -compose-x-common = ">=1.2,<2.0" +compose-x-common = ">=1.4,<2.0" importlib-resources = ">=6.1,<7.0" jsonschema = ">=4.15,<5.0" PyYAML = ">=5,<7.0" diff --git a/pyproject.toml b/pyproject.toml index beba8ab20..dff2f061a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -39,7 +39,7 @@ python = ">=3.9,<4.0" boto3 = ">=1.26,<2.0" troposphere = ">=4.5.3,<5.0" ecr-scan-reporter = { version = "^0.4.8", optional = true } -compose-x-render = "^1.0" +compose-x-render = "^1.1" compose-x-common = "^1.4" jsonschema = ">=4.21" requests = "^2.28" diff --git a/tests/features/features/events.feature b/tests/features/features/events.feature index 565b1c05d..176180c3b 100644 --- a/tests/features/features/events.feature +++ b/tests/features/features/events.feature @@ -13,16 +13,3 @@ Feature: ecs_composex.events | use-cases/blog.features.yml | use-cases/events/simple.yml | | use-cases/blog.features.yml | use-cases/events/mixed.yml | | use-cases/blog.features.yml | use-cases/events/multi_rules_same_service.yaml | - - @events - Scenario Outline: LEGACY Working with events tasks & services - Given With - And With - And I use defined files as input to define execution settings - Then I render the docker-compose to composex to validate - And I render all files to verify execution - - Examples: - | file_path | override_file | - | use-cases/blog.features.yml | use-cases/events/simple_legacy.yml | - | use-cases/blog.features.yml | use-cases/events/mixed_legacy.yml | diff --git a/use-cases/events/multi_rules_same_service.yaml b/use-cases/events/multi_rules_same_service.yaml index 646302c3b..4c11a2b25 100644 --- a/use-cases/events/multi_rules_same_service.yaml +++ b/use-cases/events/multi_rules_same_service.yaml @@ -8,7 +8,7 @@ x-events: ScheduleExpression: "cron(0 8 * * ? *)" State: "ENABLED" Services: - - name: app03 + app03: TaskCount: 2 DeleteDefaultService: True