diff --git a/schema/compose-spec.json b/schema/compose-spec.json index 33a79e9b..a6a9a1fc 100644 --- a/schema/compose-spec.json +++ b/schema/compose-spec.json @@ -13,7 +13,6 @@ "name": { "type": "string", - "pattern": "^[a-z0-9][a-z0-9_-]*$", "description": "define the Compose project name, until user defines one explicitly." }, @@ -94,7 +93,7 @@ "develop": {"$ref": "#/definitions/development"}, "deploy": {"$ref": "#/definitions/deployment"}, "annotations": {"$ref": "#/definitions/list_or_dict"}, - "attach": {"type": "boolean"}, + "attach": {"type": ["boolean", "string"]}, "build": { "oneOf": [ {"type": "string"}, @@ -110,15 +109,15 @@ "labels": {"$ref": "#/definitions/list_or_dict"}, "cache_from": {"type": "array", "items": {"type": "string"}}, "cache_to": {"type": "array", "items": {"type": "string"}}, - "no_cache": {"type": "boolean"}, + "no_cache": {"type": ["boolean", "string"]}, "additional_contexts": {"$ref": "#/definitions/list_or_dict"}, "network": {"type": "string"}, - "pull": {"type": "boolean"}, + "pull": {"type": ["boolean", "string"]}, "target": {"type": "string"}, "shm_size": {"type": ["integer", "string"]}, "extra_hosts": {"$ref": "#/definitions/list_or_dict"}, "isolation": {"type": "string"}, - "privileged": {"type": "boolean"}, + "privileged": {"type": ["boolean", "string"]}, "secrets": {"$ref": "#/definitions/service_config_or_secret"}, "tags": {"type": "array", "items": {"type": "string"}}, "ulimits": {"$ref": "#/definitions/ulimits"}, @@ -148,7 +147,7 @@ "type": "array", "items": {"$ref": "#/definitions/blkio_limit"} }, - "weight": {"type": "integer"}, + "weight": {"type": ["integer", "string"]}, "weight_device": { "type": "array", "items": {"$ref": "#/definitions/blkio_weight"} @@ -163,8 +162,14 @@ "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_count": {"oneOf": [ + {"type": "string"}, + {"type": "integer", "minimum": 0} + ]}, + "cpu_percent": {"oneOf": [ + {"type": "string"}, + {"type": "integer", "minimum": 0, "maximum": 100} + ]}, "cpu_shares": {"type": ["number", "string"]}, "cpu_quota": {"type": ["number", "string"]}, "cpu_period": {"type": ["number", "string"]}, @@ -193,7 +198,7 @@ "type": "object", "additionalProperties": false, "properties": { - "restart": {"type": "boolean"}, + "restart": {"type": ["boolean", "string"]}, "required": { "type": "boolean", "default": true @@ -254,7 +259,7 @@ "healthcheck": {"$ref": "#/definitions/healthcheck"}, "hostname": {"type": "string"}, "image": {"type": "string"}, - "init": {"type": "boolean"}, + "init": {"type": ["boolean", "string"]}, "ipc": {"type": "string"}, "isolation": {"type": "string"}, "labels": {"$ref": "#/definitions/list_or_dict"}, @@ -277,7 +282,7 @@ "mac_address": {"type": "string"}, "mem_limit": {"type": ["number", "string"]}, "mem_reservation": {"type": ["string", "integer"]}, - "mem_swappiness": {"type": "integer"}, + "mem_swappiness": {"type": ["integer", "string"]}, "memswap_limit": {"type": ["number", "string"]}, "network_mode": {"type": "string"}, "networks": { @@ -315,8 +320,11 @@ } ] }, - "oom_kill_disable": {"type": "boolean"}, - "oom_score_adj": {"type": "integer", "minimum": -1000, "maximum": 1000}, + "oom_kill_disable": {"type": ["boolean", "string"]}, + "oom_score_adj": {"oneOf": [ + {"type": "string"}, + {"type": "integer", "minimum": -1000, "maximum": 1000} + ]}, "pid": {"type": ["string", "null"]}, "pids_limit": {"type": ["number", "string"]}, "platform": {"type": "string"}, @@ -324,15 +332,15 @@ "type": "array", "items": { "oneOf": [ - {"type": "number", "format": "ports"}, - {"type": "string", "format": "ports"}, + {"type": "number"}, + {"type": "string"}, { "type": "object", "properties": { "name": {"type": "string"}, "mode": {"type": "string"}, "host_ip": {"type": "string"}, - "target": {"type": "integer"}, + "target": {"type": ["integer", "string"]}, "published": {"type": ["string", "integer"]}, "protocol": {"type": "string"}, "app_protocol": {"type": "string"} @@ -344,29 +352,29 @@ }, "uniqueItems": true }, - "privileged": {"type": "boolean"}, + "privileged": {"type": ["boolean", "string"]}, "profiles": {"$ref": "#/definitions/list_of_strings"}, "pull_policy": {"type": "string", "enum": [ "always", "never", "if_not_present", "build", "missing" ]}, - "read_only": {"type": "boolean"}, + "read_only": {"type": ["boolean", "string"]}, "restart": {"type": "string"}, "runtime": { "type": "string" }, "scale": { - "type": "integer" + "type": ["integer", "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"}, + "stdin_open": {"type": ["boolean", "string"]}, + "stop_grace_period": {"type": "string"}, "stop_signal": {"type": "string"}, "storage_opt": {"type": "object"}, "tmpfs": {"$ref": "#/definitions/string_or_list"}, - "tty": {"type": "boolean"}, + "tty": {"type": ["boolean", "string"]}, "ulimits": {"$ref": "#/definitions/ulimits"}, "user": {"type": "string"}, "uts": {"type": "string"}, @@ -383,13 +391,13 @@ "type": {"type": "string"}, "source": {"type": "string"}, "target": {"type": "string"}, - "read_only": {"type": "boolean"}, + "read_only": {"type": ["boolean", "string"]}, "consistency": {"type": "string"}, "bind": { "type": "object", "properties": { "propagation": {"type": "string"}, - "create_host_path": {"type": "boolean"}, + "create_host_path": {"type": ["boolean", "string"]}, "selinux": {"type": "string", "enum": ["z", "Z"]} }, "additionalProperties": false, @@ -398,7 +406,7 @@ "volume": { "type": "object", "properties": { - "nocopy": {"type": "boolean"}, + "nocopy": {"type": ["boolean", "string"]}, "subpath": {"type": "string"} }, "additionalProperties": false, @@ -413,7 +421,7 @@ {"type": "string"} ] }, - "mode": {"type": "number"} + "mode": {"type": ["number", "string"]} }, "additionalProperties": false, "patternProperties": {"^x-": {}} @@ -441,18 +449,18 @@ "id": "#/definitions/healthcheck", "type": "object", "properties": { - "disable": {"type": "boolean"}, - "interval": {"type": "string", "format": "duration"}, - "retries": {"type": "number"}, + "disable": {"type": ["boolean", "string"]}, + "interval": {"type": "string"}, + "retries": {"type": ["number", "string"]}, "test": { "oneOf": [ {"type": "string"}, {"type": "array", "items": {"type": "string"}} ] }, - "timeout": {"type": "string", "format": "duration"}, - "start_period": {"type": "string", "format": "duration"}, - "start_interval": {"type": "string", "format": "duration"} + "timeout": {"type": "string"}, + "start_period": {"type": "string"}, + "start_interval": {"type": "string"} }, "additionalProperties": false, "patternProperties": {"^x-": {}} @@ -484,16 +492,16 @@ "properties": { "mode": {"type": "string"}, "endpoint_mode": {"type": "string"}, - "replicas": {"type": "integer"}, + "replicas": {"type": ["integer", "string"]}, "labels": {"$ref": "#/definitions/list_or_dict"}, "rollback_config": { "type": "object", "properties": { - "parallelism": {"type": "integer"}, - "delay": {"type": "string", "format": "duration"}, + "parallelism": {"type": ["integer", "string"]}, + "delay": {"type": "string"}, "failure_action": {"type": "string"}, - "monitor": {"type": "string", "format": "duration"}, - "max_failure_ratio": {"type": "number"}, + "monitor": {"type": "string"}, + "max_failure_ratio": {"type": ["number", "string"]}, "order": {"type": "string", "enum": [ "start-first", "stop-first" ]} @@ -504,11 +512,11 @@ "update_config": { "type": "object", "properties": { - "parallelism": {"type": "integer"}, - "delay": {"type": "string", "format": "duration"}, + "parallelism": {"type": ["integer", "string"]}, + "delay": {"type": "string"}, "failure_action": {"type": "string"}, - "monitor": {"type": "string", "format": "duration"}, - "max_failure_ratio": {"type": "number"}, + "monitor": {"type": "string"}, + "max_failure_ratio": {"type": ["number", "string"]}, "order": {"type": "string", "enum": [ "start-first", "stop-first" ]} @@ -524,7 +532,7 @@ "properties": { "cpus": {"type": ["number", "string"]}, "memory": {"type": "string"}, - "pids": {"type": "integer"} + "pids": {"type": ["integer", "string"]} }, "additionalProperties": false, "patternProperties": {"^x-": {}} @@ -548,9 +556,9 @@ "type": "object", "properties": { "condition": {"type": "string"}, - "delay": {"type": "string", "format": "duration"}, - "max_attempts": {"type": "integer"}, - "window": {"type": "string", "format": "duration"} + "delay": {"type": "string"}, + "max_attempts": {"type": ["integer", "string"]}, + "window": {"type": "string"} }, "additionalProperties": false, "patternProperties": {"^x-": {}} @@ -570,7 +578,7 @@ "patternProperties": {"^x-": {}} } }, - "max_replicas_per_node": {"type": "integer"} + "max_replicas_per_node": {"type": ["integer", "string"]} }, "additionalProperties": false, "patternProperties": {"^x-": {}} @@ -590,7 +598,7 @@ "type": "object", "properties": { "kind": {"type": "string"}, - "value": {"type": "number"} + "value": {"type": ["number", "string"]} }, "additionalProperties": false, "patternProperties": {"^x-": {}} @@ -655,7 +663,7 @@ "items": { "type": "object", "properties": { - "subnet": {"type": "string", "format": "subnet_ip_address"}, + "subnet": {"type": "string"}, "ip_range": {"type": "string"}, "gateway": {"type": "string"}, "aux_addresses": { @@ -678,7 +686,7 @@ "patternProperties": {"^x-": {}} }, "external": { - "type": ["boolean", "object"], + "type": ["boolean", "string", "object"], "properties": { "name": { "deprecated": true, @@ -688,9 +696,9 @@ "additionalProperties": false, "patternProperties": {"^x-": {}} }, - "internal": {"type": "boolean"}, - "enable_ipv6": {"type": "boolean"}, - "attachable": {"type": "boolean"}, + "internal": {"type": ["boolean", "string"]}, + "enable_ipv6": {"type": ["boolean", "string"]}, + "attachable": {"type": ["boolean", "string"]}, "labels": {"$ref": "#/definitions/list_or_dict"} }, "additionalProperties": false, @@ -710,7 +718,7 @@ } }, "external": { - "type": ["boolean", "object"], + "type": ["boolean", "string", "object"], "properties": { "name": { "deprecated": true, @@ -734,7 +742,7 @@ "environment": {"type": "string"}, "file": {"type": "string"}, "external": { - "type": ["boolean", "object"], + "type": ["boolean", "string", "object"], "properties": { "name": {"type": "string"} } @@ -762,7 +770,7 @@ "environment": {"type": "string"}, "file": {"type": "string"}, "external": { - "type": ["boolean", "object"], + "type": ["boolean", "string", "object"], "properties": { "name": { "deprecated": true, @@ -801,7 +809,7 @@ "type": "string" }, "required": { - "type": "boolean", + "type": ["boolean", "string"], "default": true } }, @@ -855,7 +863,7 @@ "type": "object", "properties": { "path": {"type": "string"}, - "weight": {"type": "integer"} + "weight": {"type": ["integer", "string"]} }, "additionalProperties": false }, @@ -871,7 +879,7 @@ "target": {"type": "string"}, "uid": {"type": "string"}, "gid": {"type": "string"}, - "mode": {"type": "number"} + "mode": {"type": ["number", "string"]} }, "additionalProperties": false, "patternProperties": {"^x-": {}} @@ -884,12 +892,12 @@ "patternProperties": { "^[a-z]+$": { "oneOf": [ - {"type": "integer"}, + {"type": ["integer", "string"]}, { "type": "object", "properties": { - "hard": {"type": "integer"}, - "soft": {"type": "integer"} + "hard": {"type": ["integer", "string"]}, + "soft": {"type": ["integer", "string"]} }, "required": ["soft", "hard"], "additionalProperties": false, diff --git a/schema/schema_test.go b/schema/schema_test.go index 8467cdef..902805b9 100644 --- a/schema/schema_test.go +++ b/schema/schema_test.go @@ -17,8 +17,10 @@ package schema import ( + "os" "testing" + "gopkg.in/yaml.v3" "gotest.tools/v3/assert" ) @@ -233,3 +235,12 @@ func TestValidateRollbackConfigWithUpdateConfigFull(t *testing.T) { assert.NilError(t, Validate(config)) assert.NilError(t, Validate(config)) } + +func TestValidateVariables(t *testing.T) { + bytes, err := os.ReadFile("using-variables.yaml") + assert.NilError(t, err) + var config dict + err = yaml.Unmarshal(bytes, &config) + assert.NilError(t, err) + assert.NilError(t, Validate(config)) +} diff --git a/schema/using-variables.yaml b/schema/using-variables.yaml new file mode 100644 index 00000000..3f302cd6 --- /dev/null +++ b/schema/using-variables.yaml @@ -0,0 +1,123 @@ +name: ${VARIABLE} +services: + foo: + deploy: + mode: ${VARIABLE} + replicas: ${VARIABLE} + rollback_config: + parallelism: ${VARIABLE} + delay: ${VARIABLE} + failure_action: ${VARIABLE} + monitor: ${VARIABLE} + max_failure_ratio: ${VARIABLE} + update_config: + parallelism: ${VARIABLE} + delay: ${VARIABLE} + failure_action: ${VARIABLE} + monitor: ${VARIABLE} + max_failure_ratio: ${VARIABLE} + resources: + limits: + memory: ${VARIABLE} + reservations: + memory: ${VARIABLE} + generic_resources: + - discrete_resource_spec: + kind: ${VARIABLE} + value: ${VARIABLE} + - discrete_resource_spec: + kind: ${VARIABLE} + value: ${VARIABLE} + restart_policy: + condition: ${VARIABLE} + delay: ${VARIABLE} + max_attempts: ${VARIABLE} + window: ${VARIABLE} + placement: + max_replicas_per_node: ${VARIABLE} + preferences: + - spread: ${VARIABLE} + endpoint_mode: ${VARIABLE} + expose: + - ${VARIABLE} + external_links: + - ${VARIABLE} + extra_hosts: + - ${VARIABLE} + hostname: ${VARIABLE} + + healthcheck: + test: ${VARIABLE} + interval: ${VARIABLE} + timeout: ${VARIABLE} + retries: ${VARIABLE} + start_period: ${VARIABLE} + start_interval: ${VARIABLE} + image: ${VARIABLE} + mac_address: ${VARIABLE} + networks: + some-network: + aliases: + - ${VARIABLE} + other-network: + ipv4_address: ${VARIABLE} + ipv6_address: ${VARIABLE} + mac_address: ${VARIABLE} + ports: + - ${VARIABLE} + privileged: ${VARIABLE} + read_only: ${VARIABLE} + restart: ${VARIABLE} + secrets: + - source: ${VARIABLE} + target: ${VARIABLE} + uid: ${VARIABLE} + gid: ${VARIABLE} + mode: ${VARIABLE} + stdin_open: ${VARIABLE} + stop_grace_period: ${VARIABLE} + stop_signal: ${VARIABLE} + storage_opt: + size: ${VARIABLE} + sysctls: + net.core.somaxconn: ${VARIABLE} + tmpfs: + - ${VARIABLE} + tty: ${VARIABLE} + ulimits: + nproc: ${VARIABLE} + nofile: + soft: ${VARIABLE} + hard: ${VARIABLE} + user: ${VARIABLE} + volumes: + - ${VARIABLE}:${VARIABLE} + - type: tmpfs + target: ${VARIABLE} + tmpfs: + size: ${VARIABLE} + +networks: + network: + ipam: + driver: ${VARIABLE} + config: + - subnet: ${VARIABLE} + ip_range: ${VARIABLE} + gateway: ${VARIABLE} + aux_addresses: + host1: ${VARIABLE} + external-network: + external: ${VARIABLE} + +volumes: + external-volume: + external: ${VARIABLE} + +configs: + config1: + external: ${VARIABLE} + +secrets: + secret1: + external: ${VARIABLE}