From c43f3e10ddfb9497886b5943ae54a43e9534edc3 Mon Sep 17 00:00:00 2001 From: Xyphuz Date: Fri, 27 May 2022 14:17:04 +0800 Subject: [PATCH] feat: add spec --- compose_viz/cli.py | 2 +- compose_viz/graph.py | 2 +- compose_viz/models/__init__.py | 0 compose_viz/{ => models}/compose.py | 2 +- compose_viz/{ => models}/extends.py | 0 compose_viz/{ => models}/port.py | 0 compose_viz/{ => models}/service.py | 6 +- compose_viz/{ => models}/viz_formats.py | 0 compose_viz/{ => models}/volume.py | 0 compose_viz/parser.py | 8 +- compose_viz/spec/__init__.py | 0 compose_viz/spec/compose-spec.json | 813 ++++++++++++++++++++++++ compose_viz/spec/compose_spec.py | 550 ++++++++++++++++ pyproject.toml | 4 + tests/test_extends.py | 2 +- tests/test_parse_file.py | 10 +- tests/test_port.py | 2 +- tests/test_volume.py | 2 +- 18 files changed, 1385 insertions(+), 18 deletions(-) create mode 100644 compose_viz/models/__init__.py rename compose_viz/{ => models}/compose.py (81%) rename compose_viz/{ => models}/extends.py (100%) rename compose_viz/{ => models}/port.py (100%) rename compose_viz/{ => models}/service.py (89%) rename compose_viz/{ => models}/viz_formats.py (100%) rename compose_viz/{ => models}/volume.py (100%) create mode 100644 compose_viz/spec/__init__.py create mode 100644 compose_viz/spec/compose-spec.json create mode 100644 compose_viz/spec/compose_spec.py diff --git a/compose_viz/cli.py b/compose_viz/cli.py index ffb10bd..618c6cc 100644 --- a/compose_viz/cli.py +++ b/compose_viz/cli.py @@ -4,8 +4,8 @@ import typer from compose_viz import __app_name__, __version__ from compose_viz.graph import Graph +from compose_viz.models.viz_formats import VizFormats from compose_viz.parser import Parser -from compose_viz.viz_formats import VizFormats app = typer.Typer( invoke_without_command=True, diff --git a/compose_viz/graph.py b/compose_viz/graph.py index 32c5569..3f634f2 100644 --- a/compose_viz/graph.py +++ b/compose_viz/graph.py @@ -2,7 +2,7 @@ from typing import Optional import graphviz -from compose_viz.compose import Compose +from compose_viz.models.compose import Compose def apply_vertex_style(type) -> dict: diff --git a/compose_viz/models/__init__.py b/compose_viz/models/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/compose_viz/compose.py b/compose_viz/models/compose.py similarity index 81% rename from compose_viz/compose.py rename to compose_viz/models/compose.py index 03e8f74..441b5e6 100644 --- a/compose_viz/compose.py +++ b/compose_viz/models/compose.py @@ -1,6 +1,6 @@ from typing import List -from compose_viz.service import Service +from compose_viz.models.service import Service class Compose: diff --git a/compose_viz/extends.py b/compose_viz/models/extends.py similarity index 100% rename from compose_viz/extends.py rename to compose_viz/models/extends.py diff --git a/compose_viz/port.py b/compose_viz/models/port.py similarity index 100% rename from compose_viz/port.py rename to compose_viz/models/port.py diff --git a/compose_viz/service.py b/compose_viz/models/service.py similarity index 89% rename from compose_viz/service.py rename to compose_viz/models/service.py index b1bf84b..89740ba 100644 --- a/compose_viz/service.py +++ b/compose_viz/models/service.py @@ -1,8 +1,8 @@ from typing import List, Optional -from compose_viz.extends import Extends -from compose_viz.port import Port -from compose_viz.volume import Volume +from compose_viz.models.extends import Extends +from compose_viz.models.port import Port +from compose_viz.models.volume import Volume class Service: diff --git a/compose_viz/viz_formats.py b/compose_viz/models/viz_formats.py similarity index 100% rename from compose_viz/viz_formats.py rename to compose_viz/models/viz_formats.py diff --git a/compose_viz/volume.py b/compose_viz/models/volume.py similarity index 100% rename from compose_viz/volume.py rename to compose_viz/models/volume.py diff --git a/compose_viz/parser.py b/compose_viz/parser.py index 757d3ed..96e2667 100644 --- a/compose_viz/parser.py +++ b/compose_viz/parser.py @@ -3,10 +3,10 @@ from typing import Dict, List, Optional from ruamel.yaml import YAML -from compose_viz.compose import Compose, Service -from compose_viz.extends import Extends -from compose_viz.port import Port, Protocol -from compose_viz.volume import Volume, VolumeType +from compose_viz.models.compose import Compose, Service +from compose_viz.models.extends import Extends +from compose_viz.models.port import Port, Protocol +from compose_viz.models.volume import Volume, VolumeType class Parser: diff --git a/compose_viz/spec/__init__.py b/compose_viz/spec/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/compose_viz/spec/compose-spec.json b/compose_viz/spec/compose-spec.json new file mode 100644 index 0000000..d83dc2d --- /dev/null +++ b/compose_viz/spec/compose-spec.json @@ -0,0 +1,813 @@ +{ + "$schema": "http://json-schema.org/draft/2019-09/schema#", + "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": "declared for backward compatibility, ignored." + }, + + "name": { + "type": "string", + "description": "define the Compose project name, until user defines one explicitly." + }, + + "services": { + "id": "#/properties/services", + "type": "object", + "patternProperties": { + "^[a-zA-Z0-9._-]+$": { + "$ref": "#/definitions/service" + } + }, + "additionalProperties": false + }, + + "networks": { + "id": "#/properties/networks", + "type": "object", + "patternProperties": { + "^[a-zA-Z0-9._-]+$": { + "$ref": "#/definitions/network" + } + } + }, + + "volumes": { + "id": "#/properties/volumes", + "type": "object", + "patternProperties": { + "^[a-zA-Z0-9._-]+$": { + "$ref": "#/definitions/volume" + } + }, + "additionalProperties": false + }, + + "secrets": { + "id": "#/properties/secrets", + "type": "object", + "patternProperties": { + "^[a-zA-Z0-9._-]+$": { + "$ref": "#/definitions/secret" + } + }, + "additionalProperties": false + }, + + "configs": { + "id": "#/properties/configs", + "type": "object", + "patternProperties": { + "^[a-zA-Z0-9._-]+$": { + "$ref": "#/definitions/config" + } + }, + "additionalProperties": false + } + }, + + "patternProperties": {"^x-": {}}, + "additionalProperties": false, + + "definitions": { + + "service": { + "id": "#/definitions/service", + "type": "object", + + "properties": { + "deploy": {"$ref": "#/definitions/deployment"}, + "build": { + "oneOf": [ + {"type": "string"}, + { + "type": "object", + "properties": { + "context": {"type": "string"}, + "dockerfile": {"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"}, + "network": {"type": "string"}, + "pull": {"type": "boolean"}, + "target": {"type": "string"}, + "shm_size": {"type": ["integer", "string"]}, + "extra_hosts": {"$ref": "#/definitions/list_or_dict"}, + "isolation": {"type": "string"}, + "secrets": {"$ref": "#/definitions/service_config_or_secret"}, + "tags":{"type": "array", "items": {"type": "string"}} + }, + "additionalProperties": false, + "patternProperties": {"^x-": {}} + } + ] + }, + "blkio_config": { + "type": "object", + "properties": { + "device_read_bps": { + "type": "array", + "items": {"$ref": "#/definitions/blkio_limit"} + }, + "device_read_iops": { + "type": "array", + "items": {"$ref": "#/definitions/blkio_limit"} + }, + "device_write_bps": { + "type": "array", + "items": {"$ref": "#/definitions/blkio_limit"} + }, + "device_write_iops": { + "type": "array", + "items": {"$ref": "#/definitions/blkio_limit"} + }, + "weight": {"type": "integer"}, + "weight_device": { + "type": "array", + "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": {"$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"} + }, + "additionalProperties": false, + "patternProperties": {"^x-": {}} + }, + "depends_on": { + "oneOf": [ + {"$ref": "#/definitions/list_of_strings"}, + { + "type": "object", + "additionalProperties": false, + "patternProperties": { + "^[a-zA-Z0-9._-]+$": { + "type": "object", + "additionalProperties": false, + "properties": { + "condition": { + "type": "string", + "enum": ["service_started", "service_healthy", "service_completed_successfully"] + } + }, + "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"}, + + "expose": { + "type": "array", + "items": { + "type": ["string", "number"], + "format": "expose" + }, + "uniqueItems": true + }, + "extends": { + "oneOf": [ + {"type": "string"}, + { + "type": "object", + + "properties": { + "service": {"type": "string"}, + "file": {"type": "string"} + }, + "required": ["service"], + "additionalProperties": false + } + ] + }, + "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"}, "uniqueItems": true}, + "logging": { + "type": "object", + + "properties": { + "driver": {"type": "string"}, + "options": { + "type": "object", + "patternProperties": { + "^.+$": {"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"}, + "networks": { + "oneOf": [ + {"$ref": "#/definitions/list_of_strings"}, + { + "type": "object", + "patternProperties": { + "^[a-zA-Z0-9._-]+$": { + "oneOf": [ + { + "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"} + }, + "additionalProperties": false, + "patternProperties": {"^x-": {}} + }, + {"type": "null"} + ] + } + }, + "additionalProperties": false + } + ] + }, + "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": "object", + "properties": { + "mode": {"type": "string"}, + "host_ip": {"type": "string"}, + "target": {"type": "integer"}, + "published": {"type": ["string", "integer"]}, + "protocol": {"type": "string"} + }, + "additionalProperties": false, + "patternProperties": {"^x-": {}} + } + ] + }, + "uniqueItems": true + }, + "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": {"$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": { + "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"}, + "bind": { + "type": "object", + "properties": { + "propagation": {"type": "string"}, + "create_host_path": {"type": "boolean"}, + "selinux": {"type": "string", "enum": ["z", "Z"]} + }, + "additionalProperties": false, + "patternProperties": {"^x-": {}} + }, + "volume": { + "type": "object", + "properties": { + "nocopy": {"type": "boolean"} + }, + "additionalProperties": false, + "patternProperties": {"^x-": {}} + }, + "tmpfs": { + "type": "object", + "properties": { + "size": { + "oneOf": [ + {"type": "integer", "minimum": 0}, + {"type": "string"} + ] + }, + "mode": {"type": "number"} + }, + "additionalProperties": false, + "patternProperties": {"^x-": {}} + } + }, + "additionalProperties": false, + "patternProperties": {"^x-": {}} + } + ] + }, + "uniqueItems": true + }, + "volumes_from": { + "type": "array", + "items": {"type": "string"}, + "uniqueItems": true + }, + "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"}, + "test": { + "oneOf": [ + {"type": "string"}, + {"type": "array", "items": {"type": "string"}} + ] + }, + "timeout": {"type": "string", "format": "duration"}, + "start_period": {"type": "string", "format": "duration"} + }, + "additionalProperties": false, + "patternProperties": {"^x-": {}} + }, + "deployment": { + "id": "#/definitions/deployment", + "type": ["object", "null"], + "properties": { + "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" + ]} + }, + "additionalProperties": false, + "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" + ]} + }, + "additionalProperties": false, + "patternProperties": {"^x-": {}} + }, + "resources": { + "type": "object", + "properties": { + "limits": { + "type": "object", + "properties": { + "cpus": {"type": ["number", "string"]}, + "memory": {"type": "string"}, + "pids": {"type": "integer"} + }, + "additionalProperties": false, + "patternProperties": {"^x-": {}} + }, + "reservations": { + "type": "object", + "properties": { + "cpus": {"type": ["number", "string"]}, + "memory": {"type": "string"}, + "generic_resources": {"$ref": "#/definitions/generic_resources"}, + "devices": {"$ref": "#/definitions/devices"} + }, + "additionalProperties": false, + "patternProperties": {"^x-": {}} + } + }, + "additionalProperties": false, + "patternProperties": {"^x-": {}} + }, + "restart_policy": { + "type": "object", + "properties": { + "condition": {"type": "string"}, + "delay": {"type": "string", "format": "duration"}, + "max_attempts": {"type": "integer"}, + "window": {"type": "string", "format": "duration"} + }, + "additionalProperties": false, + "patternProperties": {"^x-": {}} + }, + "placement": { + "type": "object", + "properties": { + "constraints": {"type": "array", "items": {"type": "string"}}, + "preferences": { + "type": "array", + "items": { + "type": "object", + "properties": { + "spread": {"type": "string"} + }, + "additionalProperties": false, + "patternProperties": {"^x-": {}} + } + }, + "max_replicas_per_node": {"type": "integer"} + }, + "additionalProperties": false, + "patternProperties": {"^x-": {}} + } + }, + "additionalProperties": false, + "patternProperties": {"^x-": {}} + }, + + "generic_resources": { + "id": "#/definitions/generic_resources", + "type": "array", + "items": { + "type": "object", + "properties": { + "discrete_resource_spec": { + "type": "object", + "properties": { + "kind": {"type": "string"}, + "value": {"type": "number"} + }, + "additionalProperties": false, + "patternProperties": {"^x-": {}} + } + }, + "additionalProperties": false, + "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"} + }, + "additionalProperties": false, + "patternProperties": {"^x-": {}} + } + }, + + "network": { + "id": "#/definitions/network", + "type": ["object", "null"], + "properties": { + "name": {"type": "string"}, + "driver": {"type": "string"}, + "driver_opts": { + "type": "object", + "patternProperties": { + "^.+$": {"type": ["string", "number"]} + } + }, + "ipam": { + "type": "object", + "properties": { + "driver": {"type": "string"}, + "config": { + "type": "array", + "items": { + "type": "object", + "properties": { + "subnet": {"type": "string", "format": "subnet_ip_address"}, + "ip_range": {"type": "string"}, + "gateway": {"type": "string"}, + "aux_addresses": { + "type": "object", + "additionalProperties": false, + "patternProperties": {"^.+$": {"type": "string"}} + } + }, + "additionalProperties": false, + "patternProperties": {"^x-": {}} + } + }, + "options": { + "type": "object", + "additionalProperties": false, + "patternProperties": {"^.+$": {"type": "string"}} + } + }, + "additionalProperties": false, + "patternProperties": {"^x-": {}} + }, + "external": { + "type": ["boolean", "object"], + "properties": { + "name": { + "deprecated": true, + "type": "string" + } + }, + "additionalProperties": false, + "patternProperties": {"^x-": {}} + }, + "internal": {"type": "boolean"}, + "enable_ipv6": {"type": "boolean"}, + "attachable": {"type": "boolean"}, + "labels": {"$ref": "#/definitions/list_or_dict"} + }, + "additionalProperties": false, + "patternProperties": {"^x-": {}} + }, + + "volume": { + "id": "#/definitions/volume", + "type": ["object", "null"], + "properties": { + "name": {"type": "string"}, + "driver": {"type": "string"}, + "driver_opts": { + "type": "object", + "patternProperties": { + "^.+$": {"type": ["string", "number"]} + } + }, + "external": { + "type": ["boolean", "object"], + "properties": { + "name": { + "deprecated": true, + "type": "string" + } + }, + "additionalProperties": false, + "patternProperties": {"^x-": {}} + }, + "labels": {"$ref": "#/definitions/list_or_dict"} + }, + "additionalProperties": false, + "patternProperties": {"^x-": {}} + }, + + "secret": { + "id": "#/definitions/secret", + "type": "object", + "properties": { + "name": {"type": "string"}, + "file": {"type": "string"}, + "external": { + "type": ["boolean", "object"], + "properties": { + "name": {"type": "string"} + } + }, + "labels": {"$ref": "#/definitions/list_or_dict"}, + "driver": {"type": "string"}, + "driver_opts": { + "type": "object", + "patternProperties": { + "^.+$": {"type": ["string", "number"]} + } + }, + "template_driver": {"type": "string"} + }, + "additionalProperties": false, + "patternProperties": {"^x-": {}} + }, + + "config": { + "id": "#/definitions/config", + "type": "object", + "properties": { + "name": {"type": "string"}, + "file": {"type": "string"}, + "external": { + "type": ["boolean", "object"], + "properties": { + "name": { + "deprecated": true, + "type": "string" + } + } + }, + "labels": {"$ref": "#/definitions/list_or_dict"}, + "template_driver": {"type": "string"} + }, + "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": [ + { + "type": "object", + "patternProperties": { + ".+": { + "type": ["string", "number", "boolean", "null"] + } + }, + "additionalProperties": false + }, + {"type": "array", "items": {"type": "string"}, "uniqueItems": true} + ] + }, + + "blkio_limit": { + "type": "object", + "properties": { + "path": {"type": "string"}, + "rate": {"type": ["integer", "string"]} + }, + "additionalProperties": false + }, + "blkio_weight": { + "type": "object", + "properties": { + "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-": {}} + } + ] + } + }, + + "constraints": { + "service": { + "id": "#/definitions/constraints/service", + "anyOf": [ + {"required": ["build"]}, + {"required": ["image"]} + ], + "properties": { + "build": { + "required": ["context"] + } + } + } + } + } +} diff --git a/compose_viz/spec/compose_spec.py b/compose_viz/spec/compose_spec.py new file mode 100644 index 0000000..0880ac6 --- /dev/null +++ b/compose_viz/spec/compose_spec.py @@ -0,0 +1,550 @@ +# generated by datamodel-codegen: +# filename: compose-spec.json +# timestamp: 2022-05-27T05:44:40+00:00 + +from __future__ import annotations + +from enum import Enum +from typing import Any, Dict, List, Optional, Union + +from pydantic import BaseModel, Extra, Field, conint, constr + + +class CredentialSpec(BaseModel): + class Config: + extra = Extra.forbid + + config: Optional[str] = None + file: Optional[str] = None + registry: Optional[str] = None + + +class Condition(Enum): + service_started = "service_started" + service_healthy = "service_healthy" + service_completed_successfully = "service_completed_successfully" + + +class DependsOn(BaseModel): + class Config: + extra = Extra.forbid + + condition: Condition + + +class Extend(BaseModel): + class Config: + extra = Extra.forbid + + service: str + file: Optional[str] = None + + +class Logging(BaseModel): + class Config: + extra = Extra.forbid + + driver: Optional[str] = None + options: Optional[Dict[constr(regex=r"^.+$"), Optional[Union[str, float]]]] = None # type: ignore # noqa: F722 + + +class Port(BaseModel): + class Config: + extra = Extra.forbid + + mode: Optional[str] = None + host_ip: Optional[str] = None + target: Optional[int] = None + published: Optional[Union[str, int]] = None + protocol: Optional[str] = None + + +class PullPolicy(Enum): + always = "always" + never = "never" + if_not_present = "if_not_present" + build = "build" + missing = "missing" + + +class Ulimit(BaseModel): + class Config: + extra = Extra.forbid + + hard: int + soft: int + + +class Selinux(Enum): + z = "z" + Z = "Z" + + +class Bind(BaseModel): + class Config: + extra = Extra.forbid + + propagation: Optional[str] = None + create_host_path: Optional[bool] = None + selinux: Optional[Selinux] = None + + +class AdditionalVolumeOption(BaseModel): + class Config: + extra = Extra.forbid + + nocopy: Optional[bool] = None + + +class Tmpfs(BaseModel): + class Config: + extra = Extra.forbid + + size: Optional[Union[conint(ge=0), str]] = None # type: ignore + mode: Optional[float] = None + + +class ServiceVolume(BaseModel): + class Config: + extra = Extra.forbid + + type: str + source: Optional[str] = None + target: Optional[str] = None + read_only: Optional[bool] = None + consistency: Optional[str] = None + bind: Optional[Bind] = None + volume: Optional[AdditionalVolumeOption] = None + tmpfs: Optional[Tmpfs] = None + + +class Healthcheck(BaseModel): + class Config: + extra = Extra.forbid + + disable: Optional[bool] = None + interval: Optional[str] = None + retries: Optional[float] = None + test: Optional[Union[str, List[str]]] = None + timeout: Optional[str] = None + start_period: Optional[str] = None + + +class Order(Enum): + start_first = "start-first" + stop_first = "stop-first" + + +class RollbackConfig(BaseModel): + class Config: + extra = Extra.forbid + + parallelism: Optional[int] = None + delay: Optional[str] = None + failure_action: Optional[str] = None + monitor: Optional[str] = None + max_failure_ratio: Optional[float] = None + order: Optional[Order] = None + + +class ConfigOrder(Enum): + start_first = "start-first" + stop_first = "stop-first" + + +class UpdateConfig(BaseModel): + class Config: + extra = Extra.forbid + + parallelism: Optional[int] = None + delay: Optional[str] = None + failure_action: Optional[str] = None + monitor: Optional[str] = None + max_failure_ratio: Optional[float] = None + order: Optional[ConfigOrder] = None + + +class Limits(BaseModel): + class Config: + extra = Extra.forbid + + cpus: Optional[Union[float, str]] = None + memory: Optional[str] = None + pids: Optional[int] = None + + +class RestartPolicy(BaseModel): + class Config: + extra = Extra.forbid + + condition: Optional[str] = None + delay: Optional[str] = None + max_attempts: Optional[int] = None + window: Optional[str] = None + + +class Preference(BaseModel): + class Config: + extra = Extra.forbid + + spread: Optional[str] = None + + +class Placement(BaseModel): + class Config: + extra = Extra.forbid + + constraints: Optional[List[str]] = None + preferences: Optional[List[Preference]] = None + max_replicas_per_node: Optional[int] = None + + +class DiscreteResourceSpec(BaseModel): + class Config: + extra = Extra.forbid + + kind: Optional[str] = None + value: Optional[float] = None + + +class GenericResource(BaseModel): + class Config: + extra = Extra.forbid + + discrete_resource_spec: Optional[DiscreteResourceSpec] = None + + +class GenericResources(BaseModel): + __root__: List[GenericResource] + + +class ConfigItem(BaseModel): + class Config: + extra = Extra.forbid + + subnet: Optional[str] = None + ip_range: Optional[str] = None + gateway: Optional[str] = None + aux_addresses: Optional[Dict[constr(regex=r"^.+$"), str]] = None # type: ignore # noqa: F722 + + +class Ipam(BaseModel): + class Config: + extra = Extra.forbid + + driver: Optional[str] = None + config: Optional[List[ConfigItem]] = None + options: Optional[Dict[constr(regex=r"^.+$"), str]] = None # type: ignore # noqa: F722 + + +class ExternalNetwork(BaseModel): + class Config: + extra = Extra.forbid + + name: Optional[str] = None + + +class ExternalVolume(BaseModel): + class Config: + extra = Extra.forbid + + name: Optional[str] = None + + +class ExternalSecret(BaseModel): + name: Optional[str] = None + + +class ExternalConfig(BaseModel): + name: Optional[str] = None + + +class ListOfStrings(BaseModel): + __root__: List[str] = Field(..., unique_items=True) + + +class ListOrDict(BaseModel): + __root__: Union[Dict[constr(regex=r".+"), Optional[Union[str, float, bool]]], List[str]] # type: ignore # noqa: F722, E501 + + +class BlkioLimit(BaseModel): + class Config: + extra = Extra.forbid + + path: Optional[str] = None + rate: Optional[Union[int, str]] = None + + +class BlkioWeight(BaseModel): + class Config: + extra = Extra.forbid + + path: Optional[str] = None + weight: Optional[int] = None + + +class ServiceConfigOrSecretItem(BaseModel): + class Config: + extra = Extra.forbid + + source: Optional[str] = None + target: Optional[str] = None + uid: Optional[str] = None + gid: Optional[str] = None + mode: Optional[float] = None + + +class ServiceConfigOrSecret(BaseModel): + __root__: List[Union[str, ServiceConfigOrSecretItem]] + + +class Constraints(BaseModel): + __root__: Any + + +class BuildItem(BaseModel): + class Config: + extra = Extra.forbid + + context: Optional[str] = None + dockerfile: Optional[str] = None + args: Optional[ListOrDict] = None + ssh: Optional[ListOrDict] = None + labels: Optional[ListOrDict] = None + cache_from: Optional[List[str]] = None + cache_to: Optional[List[str]] = None + no_cache: Optional[bool] = None + network: Optional[str] = None + pull: Optional[bool] = None + target: Optional[str] = None + shm_size: Optional[Union[int, str]] = None + extra_hosts: Optional[ListOrDict] = None + isolation: Optional[str] = None + secrets: Optional[ServiceConfigOrSecret] = None + tags: Optional[List[str]] = None + + +class BlkioConfig(BaseModel): + class Config: + extra = Extra.forbid + + device_read_bps: Optional[List[BlkioLimit]] = None + device_read_iops: Optional[List[BlkioLimit]] = None + device_write_bps: Optional[List[BlkioLimit]] = None + device_write_iops: Optional[List[BlkioLimit]] = None + weight: Optional[int] = None + weight_device: Optional[List[BlkioWeight]] = None + + +class ServiceNetwork(BaseModel): + class Config: + extra = Extra.forbid + + aliases: Optional[ListOfStrings] = None + ipv4_address: Optional[str] = None + ipv6_address: Optional[str] = None + link_local_ips: Optional[ListOfStrings] = None + priority: Optional[float] = None + + +class Device(BaseModel): + class Config: + extra = Extra.forbid + + capabilities: Optional[ListOfStrings] = None + count: Optional[Union[str, int]] = None + device_ids: Optional[ListOfStrings] = None + driver: Optional[str] = None + options: Optional[ListOrDict] = None + + +class Devices(BaseModel): + __root__: List[Device] + + +class Network(BaseModel): + class Config: + extra = Extra.forbid + + name: Optional[str] = None + driver: Optional[str] = None + driver_opts: Optional[Dict[constr(regex=r"^.+$"), Union[str, float]]] = None # type: ignore # noqa: F722 + ipam: Optional[Ipam] = None + external: Optional[ExternalNetwork] = None + internal: Optional[bool] = None + enable_ipv6: Optional[bool] = None + attachable: Optional[bool] = None + labels: Optional[ListOrDict] = None + + +class Volume(BaseModel): + class Config: + extra = Extra.forbid + + name: Optional[str] = None + driver: Optional[str] = None + driver_opts: Optional[Dict[constr(regex=r"^.+$"), Union[str, float]]] = None # type: ignore # noqa: F722 + external: Optional[ExternalVolume] = None + labels: Optional[ListOrDict] = None + + +class Secret(BaseModel): + class Config: + extra = Extra.forbid + + name: Optional[str] = None + file: Optional[str] = None + external: Optional[ExternalSecret] = None + labels: Optional[ListOrDict] = None + driver: Optional[str] = None + driver_opts: Optional[Dict[constr(regex=r"^.+$"), Union[str, float]]] = None # type: ignore # noqa: F722 + template_driver: Optional[str] = None + + +class Config(BaseModel): + class Config: + extra = Extra.forbid + + name: Optional[str] = None + file: Optional[str] = None + external: Optional[ExternalConfig] = None + labels: Optional[ListOrDict] = None + template_driver: Optional[str] = None + + +class StringOrList(BaseModel): + __root__: Union[str, ListOfStrings] + + +class Reservations(BaseModel): + class Config: + extra = Extra.forbid + + cpus: Optional[Union[float, str]] = None + memory: Optional[str] = None + generic_resources: Optional[GenericResources] = None + devices: Optional[Devices] = None + + +class Resources(BaseModel): + class Config: + extra = Extra.forbid + + limits: Optional[Limits] = None + reservations: Optional[Reservations] = None + + +class Deployment(BaseModel): + class Config: + extra = Extra.forbid + + mode: Optional[str] = None + endpoint_mode: Optional[str] = None + replicas: Optional[int] = None + labels: Optional[ListOrDict] = None + rollback_config: Optional[RollbackConfig] = None + update_config: Optional[UpdateConfig] = None + resources: Optional[Resources] = None + restart_policy: Optional[RestartPolicy] = None + placement: Optional[Placement] = None + + +class Service(BaseModel): + class Config: + extra = Extra.forbid + + deploy: Optional[Deployment] = None + build: Optional[Union[str, BuildItem]] = None + blkio_config: Optional[BlkioConfig] = None + cap_add: Optional[List[str]] = Field(None, unique_items=True) + cap_drop: Optional[List[str]] = Field(None, unique_items=True) + cgroup_parent: Optional[str] = None + command: Optional[Union[str, List[str]]] = None + configs: Optional[ServiceConfigOrSecret] = None + container_name: Optional[str] = None + cpu_count: Optional[conint(ge=0)] = None # type: ignore + cpu_percent: Optional[conint(ge=0, le=100)] = None # type: ignore + cpu_shares: Optional[Union[float, str]] = None + cpu_quota: Optional[Union[float, str]] = None + cpu_period: Optional[Union[float, str]] = None + cpu_rt_period: Optional[Union[float, str]] = None + cpu_rt_runtime: Optional[Union[float, str]] = None + cpus: Optional[Union[float, str]] = None + cpuset: Optional[str] = None + credential_spec: Optional[CredentialSpec] = None + depends_on: Optional[Union[ListOfStrings, Dict[constr(regex=r"^[a-zA-Z0-9._-]+$"), DependsOn]]] = None # type: ignore # noqa: F722, E501 + device_cgroup_rules: Optional[ListOfStrings] = None + devices: Optional[List[str]] = Field(None, unique_items=True) + dns: Optional[StringOrList] = None + dns_opt: Optional[List[str]] = Field(None, unique_items=True) + dns_search: Optional[StringOrList] = None + domainname: Optional[str] = None + entrypoint: Optional[Union[str, List[str]]] = None + env_file: Optional[StringOrList] = None + environment: Optional[ListOrDict] = None + expose: Optional[List[Union[str, float]]] = Field(None, unique_items=True) + extends: Optional[Union[str, Extend]] = None + external_links: Optional[List[str]] = Field(None, unique_items=True) + extra_hosts: Optional[ListOrDict] = None + group_add: Optional[List[Union[str, float]]] = Field(None, unique_items=True) + healthcheck: Optional[Healthcheck] = None + hostname: Optional[str] = None + image: Optional[str] = None + init: Optional[bool] = None + ipc: Optional[str] = None + isolation: Optional[str] = None + labels: Optional[ListOrDict] = None + links: Optional[List[str]] = Field(None, unique_items=True) + logging: Optional[Logging] = None + mac_address: Optional[str] = None + mem_limit: Optional[Union[float, str]] = None + mem_reservation: Optional[Union[str, int]] = None + mem_swappiness: Optional[int] = None + memswap_limit: Optional[Union[float, str]] = None + network_mode: Optional[str] = None + networks: Optional[Union[ListOfStrings, Dict[constr(regex=r"^[a-zA-Z0-9._-]+$"), Optional[ServiceNetwork]]]] = None # type: ignore # noqa: F722, E501 + oom_kill_disable: Optional[bool] = None + oom_score_adj: Optional[conint(ge=-1000, le=1000)] = None # type: ignore + pid: Optional[Optional[str]] = None + pids_limit: Optional[Union[float, str]] = None + platform: Optional[str] = None + ports: Optional[List[Union[float, str, Port]]] = Field(None, unique_items=True) + privileged: Optional[bool] = None + profiles: Optional[ListOfStrings] = None + pull_policy: Optional[PullPolicy] = None + read_only: Optional[bool] = None + restart: Optional[str] = None + runtime: Optional[str] = None + scale: Optional[int] = None + security_opt: Optional[List[str]] = Field(None, unique_items=True) + shm_size: Optional[Union[float, str]] = None + secrets: Optional[ServiceConfigOrSecret] = None + sysctls: Optional[ListOrDict] = None + stdin_open: Optional[bool] = None + stop_grace_period: Optional[str] = None + stop_signal: Optional[str] = None + storage_opt: Optional[Dict[str, Any]] = None + tmpfs: Optional[StringOrList] = None + tty: Optional[bool] = None + ulimits: Optional[Dict[constr(regex=r"^[a-z]+$"), Union[int, Ulimit]]] = None # type: ignore # noqa: F722 + user: Optional[str] = None + userns_mode: Optional[str] = None + volumes: Optional[List[Union[str, ServiceVolume]]] = Field(None, unique_items=True) + volumes_from: Optional[List[str]] = Field(None, unique_items=True) + working_dir: Optional[str] = None + + +class ComposeSpecification(BaseModel): + class Config: + extra = Extra.forbid + + version: Optional[str] = Field(None, description="declared for backward compatibility, ignored.") + name: Optional[str] = Field( + None, + description="define the Compose project name, until user defines one explicitly.", + ) + services: Optional[Dict[constr(regex=r"^[a-zA-Z0-9._-]+$"), Service]] = None # type: ignore # noqa: F722 + networks: Optional[Dict[constr(regex=r"^[a-zA-Z0-9._-]+$"), Network]] = None # type: ignore # noqa: F722 + volumes: Optional[Dict[constr(regex=r"^[a-zA-Z0-9._-]+$"), Volume]] = None # type: ignore # noqa: F722 + secrets: Optional[Dict[constr(regex=r"^[a-zA-Z0-9._-]+$"), Secret]] = None # type: ignore # noqa: F722 + configs: Optional[Dict[constr(regex=r"^[a-zA-Z0-9._-]+$"), Config]] = None # type: ignore # noqa: F722 diff --git a/pyproject.toml b/pyproject.toml index 4ba3c53..f71b239 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,3 +30,7 @@ build-backend = "poetry.core.masonry.api" [tool.poetry.scripts] cpv = "compose_viz.cli:start_cli" + +[tool.coverage.run] +source = ["compose_viz"] +omit = ["compose_viz/spec/*"] diff --git a/tests/test_extends.py b/tests/test_extends.py index ad7e4c5..d54cd5d 100644 --- a/tests/test_extends.py +++ b/tests/test_extends.py @@ -1,6 +1,6 @@ import pytest -from compose_viz.extends import Extends +from compose_viz.models.extends import Extends def test_extend_init_normal() -> None: diff --git a/tests/test_parse_file.py b/tests/test_parse_file.py index f384789..2dfd0d8 100644 --- a/tests/test_parse_file.py +++ b/tests/test_parse_file.py @@ -1,11 +1,11 @@ import pytest -from compose_viz.compose import Compose -from compose_viz.extends import Extends +from compose_viz.models.compose import Compose +from compose_viz.models.extends import Extends +from compose_viz.models.port import Port, Protocol +from compose_viz.models.service import Service +from compose_viz.models.volume import Volume, VolumeType from compose_viz.parser import Parser -from compose_viz.port import Port, Protocol -from compose_viz.service import Service -from compose_viz.volume import Volume, VolumeType @pytest.mark.parametrize( diff --git a/tests/test_port.py b/tests/test_port.py index 159c92c..6a2afcb 100644 --- a/tests/test_port.py +++ b/tests/test_port.py @@ -1,4 +1,4 @@ -from compose_viz.port import Port, Protocol +from compose_viz.models.port import Port, Protocol def test_port_init_normal() -> None: diff --git a/tests/test_volume.py b/tests/test_volume.py index b3651ec..5e9cf9c 100644 --- a/tests/test_volume.py +++ b/tests/test_volume.py @@ -1,4 +1,4 @@ -from compose_viz.volume import Volume, VolumeType +from compose_viz.models.volume import Volume, VolumeType def test_volume_init_normal() -> None: