Convert JSON Schema
JSON Schema is a widely used, but unwieldy schema and validation format. CUE can import any JSON Schema, making it easier to read and maintain.
JSONSchema
A JSON Schema is just JSON itself. The difference is that
the Schema is a DSL (domain specific language),
meaning that it has special fields and a schema for itself. What inception!
When we import this into CUE, the cue
cli will notice this and handle it differently.
We are going to use the docker-compose
spec from compose-spec.
You can find many more examples on the Schema Store
or use any custom ones you have around.
Download the docker-compose
schema with this command:
wget https://raw.githubusercontent.com/compose-spec/compose-spec/master/schema/compose-spec.json
compose-spec.json (800+ lines)
compose-spec.json
{
"$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": "Version of the Compose specification used. Tools not implementing required version MUST reject the configuration file."
},
"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"},
"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"}
},
"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": {
"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"},
"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": "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": {
"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"},
"bind": {
"type": "object",
"properties": {
"propagation": {"type": "string"},
"create_host_path": {"type": "boolean"}
},
"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"}
]
}
},
"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"}
},
"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
},
"constraints": {
"service": {
"id": "#/definitions/constraints/service",
"anyOf": [
{"required": ["build"]},
{"required": ["image"]}
],
"properties": {
"build": {
"required": ["context"]
}
}
}
}
}
}
Converting to Cuelang
Converting JSON Schema to CUE is a one liner and a no brainer.
cue import -f -p compose -l '#ComposeSpec:' compose-spec.json
-f
forces overwriting the output file (compose-spec.cue)-p
sets the package name in the output-l
is the label to put the schema under- the last argument is the input schema
While there are a lot of fields in a docker-compose.yaml
file,
and still 400+ lines,
this CUE version is far easier to read and understand.
compose-spec.json (400+ lines)
compose-spec.cue
package compose
import "list"
#ComposeSpec: {
// Compose Specification
//
// The Compose file is a YAML file defining a multi-containers
// based application.
@jsonschema(schema="http://json-schema.org/draft/2019-09/schema#")
// Version of the Compose specification used. Tools not
// implementing required version MUST reject the configuration
// file.
version?: string
services?: {
{[=~"^[a-zA-Z0-9._-]+$" & !~"^()$"]: #service}
}
networks?: {
{[=~"^[a-zA-Z0-9._-]+$" & !~"^()$"]: #network}
...
}
volumes?: {
{[=~"^[a-zA-Z0-9._-]+$" & !~"^()$"]: #volume}
}
secrets?: {
{[=~"^[a-zA-Z0-9._-]+$" & !~"^()$"]: #secret}
}
configs?: {
{[=~"^[a-zA-Z0-9._-]+$" & !~"^()$"]: #config}
}
{[=~"^x-" & !~"^(version|services|networks|volumes|secrets|configs)$"]: _}
#service: {
deploy?: #deployment
build?: string | {
context?: string
dockerfile?: string
args?: #list_or_dict
labels?: #list_or_dict
cache_from?: [...string]
network?: string
target?: string
shm_size?: int | string
extra_hosts?: #list_or_dict
isolation?: string
{[=~"^x-" & !~"^(context|dockerfile|args|labels|cache_from|network|target|shm_size|extra_hosts|isolation)$"]: _}
}
blkio_config?: {
device_read_bps?: [...#blkio_limit]
device_read_iops?: [...#blkio_limit]
device_write_bps?: [...#blkio_limit]
device_write_iops?: [...#blkio_limit]
weight?: int
weight_device?: [...#blkio_weight]
}
cap_add?: list.UniqueItems() & [...string]
cap_drop?: list.UniqueItems() & [...string]
cgroup_parent?: string
command?: string | [...string]
configs?: [...string | {
source?: string
target?: string
uid?: string
gid?: string
mode?: number
{[=~"^x-" & !~"^(source|target|uid|gid|mode)$"]: _}
}]
container_name?: string
cpu_count?: int & >=0
cpu_percent?: int & >=0 & <=100
cpu_shares?: number | string
cpu_quota?: number | string
cpu_period?: number | string
cpu_rt_period?: number | string
cpu_rt_runtime?: number | string
cpus?: number | string
cpuset?: string
credential_spec?: {
config?: string
file?: string
registry?: string
{[=~"^x-" & !~"^(config|file|registry)$"]: _}
}
depends_on?: #list_of_strings | {
{[=~"^[a-zA-Z0-9._-]+$" & !~"^()$"]: condition: "service_started" | "service_healthy" | "service_completed_successfully"}
}
device_cgroup_rules?: #list_of_strings
devices?: list.UniqueItems() & [...string]
dns?: #string_or_list
dns_opt?: list.UniqueItems() & [...string]
dns_search?: #string_or_list
domainname?: string
entrypoint?: string | [...string]
env_file?: #string_or_list
environment?: #list_or_dict
expose?: list.UniqueItems() & [...number | string]
extends?: string | {
service: string
file?: string
}
external_links?: list.UniqueItems() & [...string]
extra_hosts?: #list_or_dict
group_add?: list.UniqueItems() & [...number | string]
healthcheck?: #healthcheck
hostname?: string
image?: string
init?: bool
ipc?: string
isolation?: string
labels?: #list_or_dict
links?: list.UniqueItems() & [...string]
logging?: {
driver?: string
options?: {
{[=~"^.+$" & !~"^()$"]: null | number | string}
...
}
{[=~"^x-" & !~"^(driver|options)$"]: _}
}
mac_address?: string
mem_limit?: number | string
mem_reservation?: int | string
mem_swappiness?: int
memswap_limit?: number | string
network_mode?: string
networks?: #list_of_strings | {
{[=~"^[a-zA-Z0-9._-]+$" & !~"^()$"]: {
aliases?: #list_of_strings, ipv4_address?: string, ipv6_address?: string, link_local_ips?: #list_of_strings, priority?: number
{[=~"^x-" & !~"^(aliases|ipv4_address|ipv6_address|link_local_ips|priority)$"]: _}
} | null
}
}
oom_kill_disable?: bool
oom_score_adj?: int & >=-1000 & <=1000
pid?: null | string
pids_limit?: number | string
platform?: string
ports?: list.UniqueItems() & [...number | string | {
mode?: string
host_ip?: string
target?: int
published?: int
protocol?: string
{[=~"^x-" & !~"^(mode|host_ip|target|published|protocol)$"]: _}
}]
privileged?: bool
profiles?: #list_of_strings
pull_policy?: "always" | "never" | "if_not_present" | "build" | "missing"
read_only?: bool
restart?: string
runtime?: string
scale?: int
security_opt?: list.UniqueItems() & [...string]
shm_size?: number | string
secrets?: [...string | {
source?: string
target?: string
uid?: string
gid?: string
mode?: number
{[=~"^x-" & !~"^(source|target|uid|gid|mode)$"]: _}
}]
sysctls?: #list_or_dict
stdin_open?: bool
stop_grace_period?: string
stop_signal?: string
storage_opt?: {
...
}
tmpfs?: #string_or_list
tty?: bool
ulimits?: {
{[=~"^[a-z]+$" & !~"^()$"]: int | {
hard: int, soft: int
{[=~"^x-" & !~"^(hard|soft)$"]: _}
}}
...
}
user?: string
userns_mode?: string
volumes?: list.UniqueItems() & [...string | {
type: string
source?: string
target?: string
read_only?: bool
consistency?: string
bind?: {
propagation?: string
create_host_path?: bool
{[=~"^x-" & !~"^(propagation|create_host_path)$"]: _}
}
volume?: {
nocopy?: bool
{[=~"^x-" & !~"^(nocopy)$"]: _}
}
tmpfs?: {
size?: int & >=0 | string
{[=~"^x-" & !~"^(size)$"]: _}
}
{[=~"^x-" & !~"^(type|source|target|read_only|consistency|bind|volume|tmpfs)$"]: _}
}]
volumes_from?: list.UniqueItems() & [...string]
working_dir?: string
{[=~"^x-" & !~"^(deploy|build|blkio_config|cap_add|cap_drop|cgroup_parent|command|configs|container_name|cpu_count|cpu_percent|cpu_shares|cpu_quota|cpu_period|cpu_rt_period|cpu_rt_runtime|cpus|cpuset|credential_spec|depends_on|device_cgroup_rules|devices|dns|dns_opt|dns_search|domainname|entrypoint|env_file|environment|expose|extends|external_links|extra_hosts|group_add|healthcheck|hostname|image|init|ipc|isolation|labels|links|logging|mac_address|mem_limit|mem_reservation|mem_swappiness|memswap_limit|network_mode|networks|oom_kill_disable|oom_score_adj|pid|pids_limit|platform|ports|privileged|profiles|pull_policy|read_only|restart|runtime|scale|security_opt|shm_size|secrets|sysctls|stdin_open|stop_grace_period|stop_signal|storage_opt|tmpfs|tty|ulimits|user|userns_mode|volumes|volumes_from|working_dir)$"]: _}
}
#healthcheck: {
disable?: bool
interval?: string
retries?: number
test?: string | [...string]
timeout?: string
start_period?: string
{[=~"^x-" & !~"^(disable|interval|retries|test|timeout|start_period)$"]: _}
}
#deployment: null | {
mode?: string
endpoint_mode?: string
replicas?: int
labels?: #list_or_dict
rollback_config?: {
parallelism?: int
delay?: string
failure_action?: string
monitor?: string
max_failure_ratio?: number
order?: "start-first" | "stop-first"
{[=~"^x-" & !~"^(parallelism|delay|failure_action|monitor|max_failure_ratio|order)$"]: _}
}
update_config?: {
parallelism?: int
delay?: string
failure_action?: string
monitor?: string
max_failure_ratio?: number
order?: "start-first" | "stop-first"
{[=~"^x-" & !~"^(parallelism|delay|failure_action|monitor|max_failure_ratio|order)$"]: _}
}
resources?: {
limits?: {
cpus?: number | string
memory?: string
{[=~"^x-" & !~"^(cpus|memory)$"]: _}
}
reservations?: {
cpus?: number | string
memory?: string
generic_resources?: #generic_resources
devices?: #devices
{[=~"^x-" & !~"^(cpus|memory|generic_resources|devices)$"]: _}
}
{[=~"^x-" & !~"^(limits|reservations)$"]: _}
}
restart_policy?: {
condition?: string
delay?: string
max_attempts?: int
window?: string
{[=~"^x-" & !~"^(condition|delay|max_attempts|window)$"]: _}
}
placement?: {
constraints?: [...string]
preferences?: [...{
spread?: string
{[=~"^x-" & !~"^(spread)$"]: _}
}]
max_replicas_per_node?: int
{[=~"^x-" & !~"^(constraints|preferences|max_replicas_per_node)$"]: _}
}
{[=~"^x-" & !~"^(mode|endpoint_mode|replicas|labels|rollback_config|update_config|resources|restart_policy|placement)$"]: _}
}
#generic_resources: [...{
discrete_resource_spec?: {
kind?: string
value?: number
{[=~"^x-" & !~"^(kind|value)$"]: _}
}
{[=~"^x-" & !~"^(discrete_resource_spec)$"]: _}
}]
#devices: [...{
capabilities?: #list_of_strings
count?: int | string
device_ids?: #list_of_strings
driver?: string
options?: #list_or_dict
{[=~"^x-" & !~"^(capabilities|count|device_ids|driver|options)$"]: _}
}]
#network: null | {
name?: string
driver?: string
driver_opts?: {
{[=~"^.+$" & !~"^()$"]: number | string}
...
}
ipam?: {
driver?: string
config?: [...{
subnet?: string
ip_range?: string
gateway?: string
aux_addresses?: {
{[=~"^.+$" & !~"^()$"]: string}
}
{[=~"^x-" & !~"^(subnet|ip_range|gateway|aux_addresses)$"]: _}
}]
options?: {
{[=~"^.+$" & !~"^()$"]: string}
}
{[=~"^x-" & !~"^(driver|config|options)$"]: _}
}
external?: bool | {
name?: string @deprecated()
{[=~"^x-" & !~"^(name)$"]: _}
}
internal?: bool
enable_ipv6?: bool
attachable?: bool
labels?: #list_or_dict
{[=~"^x-" & !~"^(name|driver|driver_opts|ipam|external|internal|enable_ipv6|attachable|labels)$"]: _}
}
#volume: null | {
name?: string
driver?: string
driver_opts?: {
{[=~"^.+$" & !~"^()$"]: number | string}
...
}
external?: bool | {
name?: string @deprecated()
{[=~"^x-" & !~"^(name)$"]: _}
}
labels?: #list_or_dict
{[=~"^x-" & !~"^(name|driver|driver_opts|external|labels)$"]: _}
}
#secret: {
name?: string
file?: string
external?: bool | {
name?: string
...
}
labels?: #list_or_dict
driver?: string
driver_opts?: {
{[=~"^.+$" & !~"^()$"]: number | string}
...
}
template_driver?: string
{[=~"^x-" & !~"^(name|file|external|labels|driver|driver_opts|template_driver)$"]: _}
}
#config: {
name?: string
file?: string
external?: bool | {
name?: string @deprecated()
...
}
labels?: #list_or_dict
template_driver?: string
{[=~"^x-" & !~"^(name|file|external|labels|template_driver)$"]: _}
}
#string_or_list: string | #list_of_strings
#list_of_strings: list.UniqueItems() & [...string]
#list_or_dict: {
{[=~".+" & !~"^()$"]: null | bool | number | string}
} | list.UniqueItems() & [...string]
#blkio_limit: {
path?: string
rate?: int | string
}
#blkio_weight: {
path?: string
weight?: int
}
#constraints: _
}
Validating with CUE
You can now validate your docker-compose.yaml
files like we did
in the Validate Configuration.
In this case, we will want to specify the schema with the -d
flag.
cue vet -d '#ComposeSpec' docker-compose.yaml compose-spec.cue
Drawbacks
CUE’s ability to import JSON Schema makes it easy to get started. However, the generated CUE doesn’t always have the greatest ergonomics for building up and validating values. This is often because the source schema is underspecified or there are unusual types. As such, you will likely find yourself building out and refining the imported schemas.
The following are taken from the docker-compose
output generated here.
Missing constraints
Sometimes a schema does not add validation. We know there is a fixed
format and valid versions for docker-compose
, it’s just not in this source.
#version?: string
Any fields
For more complex fields, we can end up with _
(top)
and have no validation for the field at all.
You’ll see this more often when importing Go
that has types with custom marshal functions.
#constraints: _
Duplication
CUE cannot easily detect code duplication and shared structure
if it is not already present in the source schema.
The following show missed opportunity to have a more
succinct schema with CUE.
This also happens with the regexp
for the fields near the top like services and volumes.
duplication.cue
#secret: {
name?: string
file?: string
external?: bool | {
name?: string
...
}
labels?: #list_or_dict
driver?: string
driver_opts?: {
{[=~"^.+$" & !~"^()$"]: number | string}
...
}
template_driver?: string
{[=~"^x-" & !~"^(name|file|external|labels|driver|driver_opts|template_driver)$"]: _}
}
#config: {
name?: string
file?: string
external?: bool | {
name?: string @deprecated()
...
}
labels?: #list_or_dict
template_driver?: string
{[=~"^x-" & !~"^(name|file|external|labels|template_driver)$"]: _}
}
shared.cue
#shared: {
name?: string
file?: string
external?: bool | {
name?: string
...
}
labels?: #list_or_dict
template_driver?: string
}
improved.cue
#secret: {
#shared
driver?: string
driver_opts?: {
{[=~"^.+$" & !~"^()$"]: number | string}
...
}
{[=~"^x-" & !~"^(name|file|external|labels|driver|driver_opts|template_driver)$"]: _}
}
#config: {
#shared
{[=~"^x-" & !~"^(name|file|external|labels|template_driver)$"]: _}
}