diff --git a/Dockerfile b/Dockerfile index 7c14bc1d6..8b8099e10 100644 --- a/Dockerfile +++ b/Dockerfile @@ -45,13 +45,14 @@ COPY docker-compose-entrypoint.sh /usr/local/bin/ ENTRYPOINT ["sh", "/usr/local/bin/docker-compose-entrypoint.sh"] COPY --from=docker-cli /usr/local/bin/docker /usr/local/bin/docker WORKDIR /code/ -# FIXME(chris-crone): virtualenv 16.3.0 breaks build, force 16.2.0 until fixed -RUN pip install virtualenv==20.0.30 -RUN pip install tox==3.19.0 +RUN pip install \ + virtualenv==20.0.30 \ + tox==3.19.0 +COPY requirements-dev.txt . COPY requirements-indirect.txt . COPY requirements.txt . -COPY requirements-dev.txt . +RUN pip install -r requirements.txt -r requirements-indirect.txt -r requirements-dev.txt COPY .pre-commit-config.yaml . COPY tox.ini . COPY setup.py . diff --git a/Makefile b/Makefile new file mode 100644 index 000000000..f673eace0 --- /dev/null +++ b/Makefile @@ -0,0 +1,57 @@ +TAG = "docker-compose:alpine-$(shell git rev-parse --short HEAD)" +GIT_VOLUME = "--volume=$(shell pwd)/.git:/code/.git" + +DOCKERFILE ?="Dockerfile" +DOCKER_BUILD_TARGET ?="build" + +UNAME_S := $(shell uname -s) +ifeq ($(UNAME_S),Linux) + BUILD_SCRIPT = linux +endif +ifeq ($(UNAME_S),Darwin) + BUILD_SCRIPT = osx +endif + +COMPOSE_SPEC_SCHEMA_PATH = "compose/config/config_schema_compose_spec.json" +COMPOSE_SPEC_RAW_URL = "https://raw.githubusercontent.com/compose-spec/compose-spec/master/schema/compose-spec.json" + +all: cli + +cli: download-compose-spec ## Compile the cli + ./script/build/$(BUILD_SCRIPT) + +download-compose-spec: ## Download the compose-spec schema from it's repo + curl -so $(COMPOSE_SPEC_SCHEMA_PATH) $(COMPOSE_SPEC_RAW_URL) + +cache-clear: ## Clear the builder cache + @docker builder prune --force --filter type=exec.cachemount --filter=unused-for=24h + +base-image: ## Builds base image + docker build -f $(DOCKERFILE) -t $(TAG) --target $(DOCKER_BUILD_TARGET) . + +lint: base-image ## Run linter + docker run --rm \ + --tty \ + $(GIT_VOLUME) \ + $(TAG) \ + tox -e pre-commit + +test-unit: base-image ## Run tests + docker run --rm \ + --tty \ + $(GIT_VOLUME) \ + $(TAG) \ + pytest -v tests/unit/ + +test: ## Run all tests + ./script/test/default + +pre-commit: lint test-unit cli + +help: ## Show help + @echo Please specify a build target. The choices are: + @grep -E '^[0-9a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' + +FORCE: + +.PHONY: all cli download-compose-spec cache-clear base-image lint test-unit test pre-commit help diff --git a/compose/config/config_schema_compose_spec.json b/compose/config/compose_spec.json similarity index 98% rename from compose/config/config_schema_compose_spec.json rename to compose/config/compose_spec.json index 43d3a3edf..268256744 100644 --- a/compose/config/config_schema_compose_spec.json +++ b/compose/config/compose_spec.json @@ -1,14 +1,16 @@ { "$schema": "http://json-schema.org/draft/2019-09/schema#", - "id": "config_schema_compose_spec.json", + "id": "compose_spec.json", "type": "object", "title": "Compose Specification", "description": "The Compose file is a YAML file defining a multi-containers based application.", + "properties": { "version": { "type": "string", "description": "Version of the Compose specification used. Tools not implementing required version MUST reject the configuration file." }, + "services": { "id": "#/properties/services", "type": "object", @@ -19,6 +21,7 @@ }, "additionalProperties": false }, + "networks": { "id": "#/properties/networks", "type": "object", @@ -28,6 +31,7 @@ } } }, + "volumes": { "id": "#/properties/volumes", "type": "object", @@ -38,6 +42,7 @@ }, "additionalProperties": false }, + "secrets": { "id": "#/properties/secrets", "type": "object", @@ -48,6 +53,7 @@ }, "additionalProperties": false }, + "configs": { "id": "#/properties/configs", "type": "object", @@ -59,12 +65,16 @@ "additionalProperties": false } }, + "patternProperties": {"^x-": {}}, "additionalProperties": false, + "definitions": { + "service": { "id": "#/definitions/service", "type": "object", + "properties": { "deploy": {"$ref": "#/definitions/deployment"}, "build": { @@ -190,7 +200,6 @@ "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"}, @@ -211,12 +220,12 @@ }, "uniqueItems": true }, - "extends": { "oneOf": [ {"type": "string"}, { "type": "object", + "properties": { "service": {"type": "string"}, "file": {"type": "string"} @@ -245,6 +254,7 @@ "links": {"type": "array", "items": {"type": "string"}, "uniqueItems": true}, "logging": { "type": "object", + "properties": { "driver": {"type": "string"}, "options": { @@ -258,7 +268,7 @@ "patternProperties": {"^x-": {}} }, "mac_address": {"type": "string"}, - "mem_limit": {"type": "string"}, + "mem_limit": {"type": ["number", "string"]}, "mem_reservation": {"type": ["string", "integer"]}, "mem_swappiness": {"type": "integer"}, "memswap_limit": {"type": ["number", "string"]}, @@ -425,9 +435,9 @@ "additionalProperties": false, "patternProperties": {"^x-": {}} } - ], - "uniqueItems": true - } + ] + }, + "uniqueItems": true }, "volumes_from": { "type": "array", @@ -558,6 +568,7 @@ "additionalProperties": false, "patternProperties": {"^x-": {}} }, + "generic_resources": { "id": "#/definitions/generic_resources", "type": "array", @@ -578,6 +589,7 @@ "patternProperties": {"^x-": {}} } }, + "network": { "id": "#/definitions/network", "type": ["object", "null"], @@ -607,10 +619,10 @@ "additionalProperties": false, "patternProperties": {"^.+$": {"type": "string"}} } - } - }, - "additionalProperties": false, - "patternProperties": {"^x-": {}} + }, + "additionalProperties": false, + "patternProperties": {"^x-": {}} + } }, "options": { "type": "object", @@ -640,6 +652,7 @@ "additionalProperties": false, "patternProperties": {"^x-": {}} }, + "volume": { "id": "#/definitions/volume", "type": ["object", "null"], @@ -668,6 +681,7 @@ "additionalProperties": false, "patternProperties": {"^x-": {}} }, + "secret": { "id": "#/definitions/secret", "type": "object", @@ -693,6 +707,7 @@ "additionalProperties": false, "patternProperties": {"^x-": {}} }, + "config": { "id": "#/definitions/config", "type": "object", @@ -714,17 +729,20 @@ "additionalProperties": false, "patternProperties": {"^x-": {}} }, + "string_or_list": { "oneOf": [ {"type": "string"}, {"$ref": "#/definitions/list_of_strings"} ] }, + "list_of_strings": { "type": "array", "items": {"type": "string"}, "uniqueItems": true }, + "list_or_dict": { "oneOf": [ { @@ -739,6 +757,7 @@ {"type": "array", "items": {"type": "string"}, "uniqueItems": true} ] }, + "blkio_limit": { "type": "object", "properties": { @@ -755,6 +774,7 @@ }, "additionalProperties": false }, + "constraints": { "service": { "id": "#/definitions/constraints/service", diff --git a/compose/config/validation.py b/compose/config/validation.py index 2b46cafe4..d9aaeda4b 100644 --- a/compose/config/validation.py +++ b/compose/config/validation.py @@ -502,13 +502,13 @@ def get_schema_path(): def load_jsonschema(version): - suffix = "compose_spec" + name = "compose_spec" if version == V1: - suffix = "v1" + name = "config_schema_v1" filename = os.path.join( get_schema_path(), - "config_schema_{}.json".format(suffix)) + "{}.json".format(name)) if not os.path.exists(filename): raise ConfigurationError( diff --git a/docker-compose.spec b/docker-compose.spec index ff739ab0d..0c2fa3dec 100644 --- a/docker-compose.spec +++ b/docker-compose.spec @@ -23,8 +23,8 @@ exe = EXE(pyz, 'DATA' ), ( - 'compose/config/config_schema_compose_spec.json', - 'compose/config/config_schema_compose_spec.json', + 'compose/config/compose_spec.json', + 'compose/config/compose_spec.json', 'DATA' ), ( diff --git a/docker-compose_darwin.spec b/docker-compose_darwin.spec index 3228884f5..248894759 100644 --- a/docker-compose_darwin.spec +++ b/docker-compose_darwin.spec @@ -32,8 +32,8 @@ coll = COLLECT(exe, 'DATA' ), ( - 'compose/config/config_schema_compose_spec.json', - 'compose/config/config_schema_compose_spec.json', + 'compose/config/compose_spec.json', + 'compose/config/compose_spec.json', 'DATA' ), ( diff --git a/requirements-dev.txt b/requirements-dev.txt index 8655e5c0a..9cc00c64e 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -7,3 +7,4 @@ mock==3.0.5 pytest==6.0.1; python_version >= '3.5' pytest==4.6.5; python_version < '3.5' pytest-cov==2.10.1 +PyYAML==5.3.1 diff --git a/tests/unit/config/config_test.py b/tests/unit/config/config_test.py index b67ecdaf4..426a7c978 100644 --- a/tests/unit/config/config_test.py +++ b/tests/unit/config/config_test.py @@ -238,7 +238,9 @@ class ConfigTest(unittest.TestCase): ) ) - assert 'Invalid top-level property "web"' in excinfo.exconly() + assert "compose.config.errors.ConfigurationError: " \ + "The Compose file 'filename.yml' is invalid because:\n" \ + "'web' does not match any of the regexes: '^x-'" in excinfo.exconly() assert VERSION_EXPLANATION in excinfo.exconly() def test_named_volume_config_empty(self):